System app 是在 Firefox OS 启动过程 中由Gecko装载的第一个 web 应用, 它会处理许多运行系统所需要的任务,因此不会局限于某一个单独的 web 应用。 这篇文档主要是讲解 Sytem如何工作。
任何一个可以用 JavaScript 编写的应用, 会最终使用 JavaScript 构建的。 -- Atwood's Law
注意: 您可以在 Gaia Github 仓库中获取到 source code for the System app
system app 是如何启动的
当 Gecko 试图启动System应用时, 它会解析System's manifest.webapp 文件,并根据 launch_path
参数(该参数在Gaia应用中一般是 /index.html) 装载 index.html 文件。 要管理整个的移动系统,Sytem app需要装载很多资源文件。
整个启动过程是从 bootstrap.js
中的下面代码开始的:
window.addEventListener('load', function startup() { // define safelyLaunchFTU function safelyLaunchFTU() { ... } if (Applications.ready) { safelyLaunchFTU(); } else { ... } window.addEventListener('ftudone', function doneWithFTU() { window.removeEventListener('ftudone', doneWithFTU); var lock = window.navigator.mozSettings.createLock(); lock.set({ 'gaia.system.checkForUpdates': true }); } ... // With all important event handlers in place, we can now notify // Gecko that we're ready for certain system services to send us // messages (e.g. the radio). var evt = new CustomEvent('mozContentEvent', { bubbles: true, cancelable: false, detail: { type: 'system-message-listener-ready' } }); window.dispatchEvent(evt); }
这段代码是这样工作的:
- System 是运行在浏览器引擎中真正的web应用, 并需要所有它依赖的资源都装载进去 — 包括 图片(images)和样式(styles)。 因此一旦
window.onload
事件触发, 我们就要开始这些工作。 - 首先,我们要使用 safelyLaunchFTU() 函数准备启动首次启动体验(FTU) 。顾名思义, 只有当用户首次启动 Firefox OS 时, FTU才会出现。
- 当用户在 FTU中点击 “完成” 时, 会启动 ftudone 自定义事件,
bootstrap.js
会监听和处理这个事件。 - 下一步, 我们会使用
Settings
API (navigator.mozSettings
) 将gaia.system.checkForUpdates
设置成true
, 表示系统会检查更新。 - 最后,我们会通过 CustomEvent 运行自定义的
window.dispatchEvent
。这是Gaia 使用的一个非常重要的设计模式, 用于系统消息的处理以及与Gecko层的通信。上面方法是指 Gaia System app 通知 Gecko层, 它已经准备好监听和处理事件了。
实现模块化
为了实现更好的模块化和灵活性,system 应用本身也在不断的改进。 从1.4 版本开始, 专门发起了一个关于 refactor the system module as instantiable object 的倡议来讨论该问题。
在所有上述代码运行后,每个对 bootstrap.js
中的系统组件的引用都是是如下的形式:
window.telephonySettings = new TelephonySettings(); window.telephonySettings.start();
TelephonySettings()
的源代码框架如下:
(function(exports) { 'use strict'; function TelephonySettings() { this.init_param = false; } TelephonySettings.prototype = { // Initialzes all settings. start: function() { ... }, // Clean all settings. stop: function() { ... }, 1st_method: function ts_1st_method() { ... }, 2nd_method: function ts_2nd_method() { ... } }; exports.TelephonySettings = TelephonySettings; }(window));
这种模式可以帮助每个系统组件实现模块化,并使它们的可测性更强。
开机和关机动画
本部分会讲解 System app是如何控制开机动画和关机动画的。 init_logo_handler.js
和 sleep_menu.js
文件会对 system 开机动画和关机动画进行处理。
开机动画
开机动画当前并没有包含在主引导程序中,而是要借助于检查 EventListeners。
在 init_logo_handler.js
文件中的开机启动代码如下所示:
一旦Gecko准备将一些东西画在屏幕上时, 会选择 logo或者是动画。当DOM装载完成时, 我们会运行合适的系统处理程序来完成log或动画。当 ftudone 或者 ftuskip 运行时会将logo隐藏。 包含在 init_logo_handler.js 中的
_appendCarrierPowerOn
方法表示了如何通过监听DOMContentLoaded事件来启动动画或启动logo。logoLoader
方法则在 logo_loader.js 中定义。
var self = this; document.addEventListener('DOMContentLoaded', function() { self.carrierLogo.appendChild(self.logoLoader.element); self._setReady(); });
一旦准备好 logo, 系统会调用 _setReady()
方法, 在方法中会建立一个监听器来监听特殊的 mozChromeEvent事件,这个事件会带有
system-first-paint
类型,表示系统系统已经准备好向屏幕绘图。
var elem = this.logoLoader.element; ... window.addEventListener('mozChromeEvent', function startVideo(e) { if (e.detail.type == 'system-first-paint') { window.removeEventListener('mozChromeEvent', startVideo); if (elem && elem.ended === false) { elem.play(); } } });
此时图形元素开始运行。一旦启动 ftuopen
或 ftuskip
事件, init_logo_handler.js
会调用默认的 handleEvent()
方法反过来触发 animate()
方法来以淡出的效果隐藏动画。
init: function ilh_init(logoLoader) { window.addEventListener('ftuopen', this); window.addEventListener('ftuskip', this); ... }, handleEvent: function ilh_handleEvent() { this.animate(); },
关机动画
当系统准备就绪时,长按电源(power)按键,会触发定义在 hardware_button.js 文件中的
holdsleep
事件。 hardware_button.js 文件会处理所有“物理按键点击“的事件,包括电源(休眠)键,home键,音量增/减键等。
HardwareButtonsSleepState.prototype.enter = function() { this.timer = setTimeout(function() { / * When the user holds Sleep button more than HOLD_INTERVAL. */ this.hardwareButtons.publish('holdsleep'); this.hardwareButtons.setState('base'); }.bind(this), this.hardwareButtons.HOLD_INTERVAL); };
关机动画是由 sleep_menu.js 处理的
;这个脚本文件会监听 holdsleep
事件,并且当它触发时会显示菜单对话框。
init: function sm_init() { ... window.addEventListener('holdsleep', this.show.bind(this)); ... }
如果用户通过重启和关机菜单选项选择关机,会触发 startPowerOff()
方法, 它反过来会触发 LogoLoader()
函数来对关机动画进行处理。
handleEvent: function sm_handleEvent(evt) { switch (evt.type) { case 'click': ... this.handler(action); break; ... } } handler: function sm_handler(action) { switch (action) { ... case 'restart': this.startPowerOff(true); break; case 'power': this.startPowerOff(false); break; ... } }
System 的功能
System app 会负责许多功能和任务, 你可能还会对能在其职权范围内找到而惊讶。系统应用负责的范围包括:状态栏( statusbar)和 实用工具盘管理(utility tray management), SIM卡锁(SIM lock), 更新管理(update manager), 主屏幕启动(homescreen launcher),webapp 窗口管理(webapp window) 等。 本节会对System 提供的一些最重要的功能进行探究。
状态栏(Statusbar)和实用工具盘(utility tray)
系统状态栏和下拉菜单 (Gaia 通常称之为 utility tray; Android 称之为 notification bar) 是由 statusbar.js
和 utility_tray.js
来处理的。 在应用的 index.html 中,状态栏条目是在
<div id="statusbar" data-z-index-level="statusbar">
中定义的,而实用工具盘条目在下面的结构中定义:
<div id="utility-tray" data-z-index-level="utility-tray"> <div id="notifications-container"> ... </div> </div>
实用工具盘(utility tray)中会有一些特殊的面板,如更新管理器,紧急回调管理器,存储监视通知,媒体播放通知和控制,蓝牙传输状态,按键输入法(IME)切换。与之相关联的处理程序和样式放置在 js
/ 和style/
文件夹中。
特殊的应用launcher
System app 有三个特殊的launchers, 在需要时会调用单独的web应用程序。
- 主屏幕启动器 : Homescreen 应用在运行时,当用户按下 Home 按钮,webapp 崩溃或者使用其他方式退出webapp时,都会显示主屏幕。
- 锁屏启动器: 运行 lockscreen 应用时, 每次当用户打开屏幕时都会显示锁屏界面。
- FTU 启动器: 运行FTU 体验应用。这个应用只会在用户使用一个全新的 FirefoxOS设备(或者将设备恢复到出厂模式)时运行一次。而且 FTU webapp不会允许用户点击Home按键退出这个应用。
锁屏
Lockscreen app 的主要入口文件是 system/js/lockscreen.js
。用户可以在这个界面解锁,启动相机以及音乐播放器。
紧急拨号器
紧急拨号器的代码是在 gaia/apps/system/emergency-call/
文件夹下的。 它是拨号应用的简化版本,允许用户可以访问 SIM PIN unlock dialog 实现紧急拨号服务。
全系统(System-wide)UI
System应用会处理绝大多数的全系统(system-wide) UI, 其中大部分包括的是一些对话框,如音量警告对话框,SIM PIN解锁对话框,小区广播(cell broadcast)对话框,还包括一些影响窗口行为的UI元素,如 software home按键。
z-index level
在System 应用的 index.html
文件中,许多组件都带有 data-z-index-level
属性,例如
<div id="sleep-menu" data-z-index-level="sleep-menu"> ... </div>
对应 z-index-levels 定义在 system/style/zindex.css 文件中,如
#screen > [data-z-index-level="sleep-menu"] { z-index: 65536; } ... #screen > [data-z-index-level="app"] > .appWindow { z-index: 3; }
z-index 设置是根据元素在屏幕上显示的顺序进行排列的 — 如果元素需要在视觉层次中比较高的元素数值也会较高。这就是System app在基本水平上窗口管理的方式。
software home 按键
software home 按键是 home按键的一种替代,会在缺少物理 home按键的情况下自动启动。例如在 Nexus 4 中。 要控制它的外观,Gecko提供了一个专有的媒体功能称为 -moz-physical-home-button,
它能够在媒体查询时使用基于硬件Home按键的外观。 如果需要,窗口管理会为 software home
按键分配一些屏幕的空间。
在 system/style/window.css
(以及许多其他的系统样式文件中), 可以看到
@media not all and (-moz-physical-home-button) { #screen:not(.software-button-disabled) > #windows > .appWindow { bottom: 5rem; } }
Home 手势 (从屏幕底部向上滑动)
home 手势是另外一个硬件home按键的替代;它可以在 开发者设置 中使能,而控制代码在 system/js/home_gesture.js 文件中。例如,这个手势涉及到从屏幕底部清扫,可以用来关闭应用。
这个手势能够在 Firefox OS 平板设备中自动使能。 而 gaia/shared/js/screen_layout.js
是专门用于检测设备是否是平板的。
音量警告对话框
在 system/js/sound_manager.js
. 中包含控制音量警告对话框的代码。如果下面所有条件都满足,则会弹出音量警告对话框:
- 耳机已插入设备
- 已超过最大音量限值 85dB
- 声道音频中有内容播放
SIM PIN 解锁对话框
控制SIM PIN 解锁对话框的代码在 system/js/simcard_dialog.js 中
— 当Passcode lock选项使能时,对话框会在锁屏界面显示。开放的应用必须要在 manifest.webapp
中包含 telephony 权限中(System app中含有该权限)。
注意: 当手机在飞行模式时,不会显示SIM PIN 解锁对话框