快应诞生背景

微信的小程序使得很多原来需要调动APP的场景不复存在,正式由于微信小程序的冲击,3月20日,华为联手九大手机厂商,共同举办了“快应用”标准启动发布会。“快应用”是几家手机厂商基于硬件平台共同推出的新型应用生态,用户不必下载安装,即点即用,能够享受到原生应用的性能体验。“快应用”使用前端技术栈开发与原生渲染,兼具H5页面和原生应用的双重优点。

快应用使用场景

进入小米应用商店,搜索“饿了么”:

点击“秒开”就可以使用快应用了

在华为应用市场搜索“快应用”:

点击查看更多:

可以看到一些快应用app已经上线了,使用起来体验也不错,体积也相当小

微信小程序VS快应用

微信小程序推出后,尽管前期受到了不少质疑,但却一直发展非常稳健。腾讯3月21日刚刚公布的2017年全年财报中披露,自2017年1月推出小程序以来,截至2018年1月已推出58万个小程序,日活跃账户超过1.7亿个。张小龙也曾经表示,未来两年内,小程序将取代80%的应用市场。如果该目标达成,这意味着微信小程序建立起一个强大的超级生态,极大挤压了国产手机厂商应用分发和数字广告业务的成长空间。因此,国产手机厂商的“快应用”主要针对微信小程序,与后者争抢用户和流量。

那么快应用要和微信小程序去竞争,它们各自又有什么优缺点,谁又会更占优势呢?我个人总结之后列出了如下几点:

微信小程序

优点:

  • 微信小程序已经在市场上取得了一定的规模效益,抢占了市场先机
  • 微信自带用户流量,用户黏度较高,通过微信打开小程序非常方便
  • 微信小程序支持iOS和安卓两大操作系统,覆盖了所有的用户群体,不同操作系统用户之间数据共享方便,比如iOS用户可以通过一个微信链接响安卓用户发送一个微信小程序链接

缺点:

  • 微信小程序基于系统上层进行的封装,在性能上和原生app差距较大

快应用

优点:

  • 基于系统层开发,将脚本转化为原生组件,运行效率更接近原声app,用户体验会更加好

缺点:

  • 只支持安卓系统,在用户群体上受到限制,也无法在安卓和iOS系统之间做到数据共享
  • 几大厂商合作,可能会产生分歧,如何去协调各大厂商,以及后期的利益分配等是一个大问题
  • 缺乏用户粘性和使用场景,如果每次打开其他应用都要去应用商店搜索快应用app的话显然不如微信小程序方便

总结:

从快应用的诞生,我们能够看到国内手机硬件厂商开始反思自己在安卓生态中的地位,寻求转型和突破以争取更大的话语权和利益。这个方向显然是对的,但是对比微信小程序,较好的性能几乎成了快应用的唯一优势,但是随着现在手机性能不断增强以及微信小程序的不断优化,这个问题将变得忽略不计,况且现在很多微信小程序用户流畅度意境做的相当不错了,而在用户群体、使用方便性等几大方面,微信小程序占据着绝对优势,而且快应用由于自生的缺陷无法弥补这几方面的劣势,所以说快应用几乎无法撼动微信小程序的地位,更不用说打败小程序,不过如果快应用能够在应用分发市场上对微信形成一定威胁并且从中分得一杯羹的话,那也能够证明快应用取得了成功。

环境配置

6.0版本以上NodeJS,官方推荐 v6.11.3 LTS

安装hap-toolkit:npm install -g hap-toolkit(帮助开发者通过命令行工具来完成工程的创建等工作),在命令行中执行hap -V会输出版本信息表示hap-toolkit安装成功,如下命令所示:

hap -V

创建项目

建好环境后,开发者就可以利用全局hap命令创建一个项目模板,如下所示,其中<ProjectName>为自定义的项目名称

hap init <ProjectName>

命令执行后,会在当前目录下创建<ProjectName>文件夹,并作为项目根目录

这个项目已经包含了项目配置与简单页面的初始代码,项目根目录结构如下:

├── node_modules
├── sign rpk包签名模块
│ └── debug 调试环境
│ ├── certificate.pem 证书文件
│ └── private.pem 私钥文件
├── src
│ ├── Common 公用的资源文件和组件文件
│ │ └── logo.png manifest.json中配置的icon
│ ├── Demo 页面目录
│ | └── index.ux 页面文件,文件名不必与父文件夹相同
│ ├── app.ux APP文件(用于包括公用资源)
│ └── manifest.json 项目配置文件(如:应用描述、接口申明、页面路由等)
└── package.json 定义项目需要的各种模块及配置信息,npm install根据这个配置文件,自动下载所需的运行和开发环境

目录的简要说明如下:

  • src:项目源文件夹
  • node_modules:项目的依赖类库
  • sign:签名模块,当前仅有debug签名,如果内测上线,请添加release文件夹,增加线上签名;签名生成方法请参考文档:编译工具openssl

编译项目

安装npm依赖

在项目根目录下,运行如下命令安装依赖包(webpack,babel等)

npm install

编译项目

在项目的根目录下,运行如下命令进行编译打包,生成rpk包

npm run build

编译打包成功后,项目根目录下会生成文件夹:build、dist

  • build:临时产出,包含编译后的页面js,图片等
  • dist:最终产出,包含rpk文件。其实是将build目录下的资源打包压缩为一个文件,后缀名为rpk,这个rpk文件就是项目编译后的最终产出

自动重新编译

如果希望每次修改源代码文件后,都自动重新编译项目,请使用如下命令:

npm run watch

手机安装调试器

调试器APK是一个Android应用程序,请从站点地址下载

说明如下:

  • 扫码安装:配置HTTP服务器地址,下载rpk包,并唤起平台运行rpk包
  • 本地安装:选择手机文件系统中的rpk包,并唤起平台运行rpk包
  • 在线更新:重新发送HTTP请求,更新rpk包,并唤起平台运行rpk包
  • 开始调试:唤起平台运行rpk包,并启动远程调试工具

注意:若无法正常使用调试器,请升级手机系统到最新版本或安装平台预览版

手机安装平台预览版

较新的系统版本中内置平台正式版,即真实的运行环境。然而,更新平台正式版的时间周期较长,开发调试平台新功能可使用平台预览版

平台预览版存在以下优缺点:

  • 优点:迭代速度快,可立即体验平台新功能
  • 缺点:实现与真实的运行环境存在差异,对厂商服务和第三方服务的支持存在缺陷

平台预览版APK是一个Android应用程序,请从站点地址下载

下载安装成功后,在调试器中点击切换运行平台至...mockup即可在平台预览版上运行rpk包

在平台上运行rpk包

在调试器中唤起平台打开rpk包有多种途径,以下两者选其一即可,推荐第一种途径:

  • HTTP请求:开发者启动HTTP服务器,打开调试器,点击扫码安装配置HTTP服务器地址,下载rpk包,并唤起平台运行rpk包
  • 本地安装:开发者将rpk包拷贝到手机文件系统,打开调试器,点击本地安装选择rpk包,并唤起平台运行rpk包

1. HTTP请求

启动HTTP服务器

在终端中新建一个窗口,进入项目的根目录运行如下命令,启动本地服务器(默认端口为12306)

npm run server

自定义端口(如:8080)

npm run server -- --port 8080

在手机上预览运行效果

配置HTTP服务器地址有两种方式,以下两者选其一即可:

  • 打开调试器 --> 点击"扫码安装",扫描终端窗口中的二维码即可完成配置(若扫描不成功,可在浏览器中打开页面:http://localhost:<your port>,扫描页面中的二维码)
  • 打开调试器 --> 点击右上角menu --> 设置,输入终端窗口中提示的HTTP服务器地址

配置完成后,若没有自动唤起平台运行rpk包,点击在线更新唤起平台运行rpk包

若提示安装失败,请检查执行npm run server的终端窗口是否正常运行

2. 本地安装

复制rpk包到手机中

<ProjectName>/dist目录下编译产出的rpk包通过USB数据线或其他方式,复制到手机文件系统中

本地安装rpk包

打开调试器 --> 点击"本地安装",选择手机文件系统中的rpk包,并自动唤起平台运行rpk包,查看效果

配置应用基本信息

每个应用都要有专属的名称,图标等,这些信息都需要在manifest.json文件中配置;详细信息请参考文档:manifest文件

应用包名(package)

应用包名,是区别于其他应用的唯一标识

推荐采用com.company.module的格式,示例如下:

{
"package": "com.example.demo"
}

应用名称(name)

应用名称,6个汉字以内,与应用商店保存的名称一致;框架提供保存到桌面的功能,桌面上显示的应用名即为此属性

示例如下:

{
"name": "发票小助手"
}

应用图标(icon)

规则为正方形(不能是圆角),且务必无白边

{
"icon": "/Common/logo.png"
}

注意:

请使用绝对路径,其中/对应于路径<ProjectName>/src/

应用版本名称、版本号(versionName、versionCode)

应用版本名称、版本号为开发者的应用包维护的版本信息

应用版本名称为主版本.次版本格式

应用版本号为整数,从1开始,每次更新上架请自增1

示例如下:

{
"versionName": "1.0",
"versionCode": 1
}

支持的最小平台版本号(minPlatformVersion)

支持的最小平台版本号为必填项,默认值为1000,标识开发者的rpk包兼容支持的最小运行平台版本

当使用了1000以上的平台版本新增特性时,就必须确保minPlatformVersion最低为该平台版本号,避免上线后在更低版本平台上运行出错

示例如下:

{
"minPlatformVersion": "1000"
}

配置接口列表(features)

在使用接口时,需要先在manifest中声明接口。在每个接口文档的顶部,都附有声明接口的配置代码

以fetch网络请求为例,示例如下:

{
"features": [
{ "name": "system.fetch" }
]
}

配置页面路由(router)

路由,用于定义页面的实际地址、跳转地址。如果ux页面没有配置路由,则不参与项目编译。一个目录下最多只能存在一个主页面文件(不包括组件文件)

首页名称(router.entry)

首页,即应用平台启动时默认打开的页面。首页需配置为应用中某页面的名称,即在<ProjectName>/src目录下,页面目录的相对路径

示例如下:

假设工程根目录如下所示

└── src
└── Demo 页面目录,存放各自页面私有的资源文件和组件文件
└── index.ux 页面文件,文件名不必与父文件夹相同(推荐index.ux)

假设首页为Demo目录下的index.ux文件,则首页对应的页面名称为Demo

{
"router": {
"entry": "Demo"
}
}

页面路由对象(router.pages)

页面路由对象,key为页面名称(<ProjectName>/src目录下,页面目录的相对路径),value为页面具体路由配置,key不要重复

页面具体路由配置(router.pages的value)包括以下属性:

  • component:页面对应的ux文件名
  • path:页面路径,不填则默认为页面名称(<ProjectName>/src目录下,页面目录的相对路径)

示例如下:

假设工程根目录如下所示

└── src
|── Demo 页面目录,存放各自页面私有的资源文件和组件文件
| └── index.ux 页面文件,文件名不必与父文件夹相同(推荐index.ux)
└── Doc
└── Layout 页面目录,存放各自页面私有的资源文件和组件文件
└── index.ux 页面文件,文件名不必与父文件夹相同(推荐index.ux)

当页面名称(router.pages的key)为Demo时,对应的页面配置(router.pages的value)包括:

  • component:页面对应的ux文件名index
  • path:页面路径,默认为页面名称Demo
{
"router": {
"pages": {
"Demo": {
"component": "index"
},
"Doc/Layout": {
"component": "index"
}
}
}
}

现在,开发者就可以通过/Demo访问到Demo目录下的index.ux页面了

配置页面UI显示(display)

UI显示,用于定义与UI显示相关的配置。支持定义:页面公用的默认UI显示、页面私有的UI显示

页面公用的默认UI显示

页面公用的默认UI显示,即被所有页面共享

以标题栏文字的配置为例:

{
"display": {
"titleBarText": "页面公用的默认标题"
}
}

未配置私有标题的页面,标题栏文字均将显示为页面公用的默认标题

页面私有的UI显示

页面私有的UI显示,在display.pages对象下配置:key为页面名称(与路由中的页面名称保持一致),value为页面私有的UI显示

以标题栏文字的配置为例:

{
"display": {
"pages": {
"Demo": {
"titleBarText": "Demo页面的标题"
}
}
}
}

manifest文件

manifest.json文件中包含了应用描述、接口声明、页面路由信息

manifest

属性
类型
默认值
必填
描述
package String - 应用包名,确认与原生应用的包名不一致,推荐采用com.company.module的格式,如:com.example.demo
name String - 应用名称,6个汉字以内,与应用商店保存的名称一致,用于在桌面图标、弹窗等处显示应用名称
icon String - 应用图标,提供192x192大小的即可
versionName String - 应用版本名称,如:"1.0"
versionCode Integer - 应用版本号,从1自增,推荐每次重新上传包时versionCode+1
minPlatformVersion Integer 1000 支持的最小平台版本号,原理同Android API Level,兼容性检查,避免上线后在低版本平台运行并导致不兼容
features Array - 接口列表,绝大部分接口都需要在这里声明,否则不能调用,详见每个接口的文档说明
config Object - 系统配置信息,详见下面说明
router Object - 路由信息,详见下面说明
display Object - UI显示相关配置,详见下面说明

config

用于定义系统配置和全局数据。

属性
类型
默认值
描述
logLevel String log 打印日志等级,分为off,error,warn,info,log,debug
designWidth Integer 750 页面设计基准宽度,根据实际设备宽度来缩放元素大小
data Object - 全局数据对象,属性名不能以$或_开头,在页面中可通过this进行访问;如果全局数据属性与页面中data属性重名,则页面初始化时,全局数据会覆盖页面中对应的属性值

router

用于定义页面的组成和相关配置信息,如果页面没有配置路由信息,则在编译打包时跳过。

属性
类型
默认值
描述
entry String - 首页名称
pages Object - 页面配置列表,key值为页面名称(对应页面目录名,例如Hello对应'Hello'目录),value为页面详细配置page,详见下面说明

router.page

用于定义单个页面路由信息。

属性
类型
默认值
必填
描述
component String - 页面对应的组件名,与ux文件名保持一致,例如'hello' 对应 'hello.ux'
path String /<页面名称> 页面路径,例如“/user”,不填则默认为/<页面名称>。
path必须唯一,不能和其他page的path相同。
下面page的path因为缺失,会被设置为“/Index”:
"Index": {"component": "index"}
filter Object - 声明页面可以处理某种请求

router.page.filter

声明页面可以处理某种请求,页面可以从$page获取打开页面的参数,参见script脚本。filter的结构如下:

"filter": {
"<action>": {
"uri": "<pattern>"
}
}
属性
类型
默认值
必填
描述
action String - 请求的动作,目前仅支持view这一种
uri Pattern - 请求的数据的匹配规则。必须是正则表达式。如https?://.*可以匹配所有http和https类型的网址

可以处理所有http和https请求的filter定义如下:

"filter": {
"view": {
"uri": "https?://.*"
}
}

display

用于定义与UI显示相关的配置。

属性
类型
默认值
描述
backgroundColor String #ffffff 窗口背景颜色
fullScreen Boolean false 是否是全屏模式,默认不会同时作用于titleBar,titleBar需要继续通过titleBar控制
titleBar Boolean true 是否显示titleBar
titleBarBackgroundColor String - 标题栏背景色
titleBarTextColor String - 标题栏文字颜色
titleBarText String - 标题栏文字(也可通过页面跳转传递参数(titleBarText)设置)
menu Boolean false 是否显示标题栏右上角菜单按钮
pages Object - 各个页面的显示样式,key为页面名(与路由中的页面名保持一致),value为窗口显示样式,页面样式覆盖default样式。

示例:

{
"package": "com.company.unit",
"name": "appName",
"icon": "/Common/icon.png",
"versionName": "1.0",
"versionCode": 1,
"minPlatformVersion": 1000,
"features": [
{ "name": "system.network" }
],
"permissions": [
{ "origin": "*" }
],
"config": {
"logLevel": "off"
},
"router": {
"entry": "Hello",
"pages": {
"Hello": {
"component": "hello",
"path": "/",
"filter": {
"view": {
"uri": "https?://.*"
}
}
}
}
},
"display": {
"backgroundColor": "#ffffff",
"fullScreen": false,
"titleBar": true,
"titleBarBackgroundColor": "#000000",
"titleBarTextColor": "#fffff",
"pages": {
"Hello": {
"backgroundColor": "#eeeeee",
"fullScreen": true,
"titleBarBackgroundColor": "#0000ff",
"titleBarText": "Hello"
}
}
}
}

源码文件

APP,页面和自定义组件均通过ux文件编写,ux文件由template模板style样式script脚本3个部分组成

app.ux

当前app.ux编译后会包含manifest配置信息(可以在npm run build之后查看文件内容),所以请不要删除/**manifest**/的注释内容标识。

您可以在<script>中引入一些公共的脚本,并暴露在当前app的对象上,如下所示,然后就可以在页面ux文件的ViewModel中,通过this.$app.util访问

<script> import util from './util.js' 
module.exports = { /**manifest**/, util: util }
</script>
页面路由
导入模块 import router from '@system.router' 或 var router = require("@system.router")

接口定义

router.push(OBJECT)

跳转到应用内的某个页面

参数:

参数
类型
必填
说明
uri String 要跳转到的uri,可以是下面的格式:
  1. 包含schema的完整uri;目前支持的schema有tel,sms和mailto,例如tel:10086。
  2. 以‘/’开头的应用内页面的路径;例:/about。
  3. 以非‘/’开头的应用内页面的名称;例:About。
  4. 特殊的,如果uri的值是"/",则跳转到path为"/"的页,没有则跳转到首页

支持包含schema的完整uri。对于带有schema的uri,处理流程如下:

  1. 查找app下所有page的filter设置来选择合适的page处理请求(参见[manifest文件](../../framework/manifest.md))
  2. 如果没有合适的page能够处理请求,会使用默认策略来处理请求。目前默认策略支持对http、https、internal这几种schema的处理
  3. 如果默认策略也不能处理请求,会尝试使用系统中的应用来处理请求
  4. 如果没有系统应用可以处理请求,会抛弃请求

默认策略的处理逻辑:

  1. 如果schema是http/https,会用内置的web页面打开网页
  2. 如果schema是internal(参见[文件组织](../../framework/file-organization.md)),会根据uri的文件扩展名来确定文件类型,再调用系统中的应用打开文件
params Object 跳转时需要传递的数据,参数可以在页面中通过this.param1的方式使用,param1为json中的参数名,param1对应的值会统一转换为String类型

示例:

// launch phone app
router.push({
uri: 'tel:10086'
});
// open page by path
router.push({
uri: '/about',
params: {testId:'1'}
});
// open page by name
router.push({
uri: 'About',
params: {testId:'1'}
});
// open web page
router.push({
uri: 'http://www.example.com'
});
// install apk
router.push({
uri: 'internal://cache/example.apk'
});

router.replace(OBJECT)

跳转到应用内的某个页面,当前页面无法返回

参数:

参数
类型
必填
说明
uri String 要跳转到的uri,可以是下面的格式:
  1. 以"/"开头的应用内页面的路径;例:/about。
  2. 以非"/"开头的应用内页面的名称;例:About。
  3. 特殊的,如果uri的值是"/",则跳转到path为"/"的页,没有则跳转到首页
params Object 跳转时需要传递的数据,参数可以在页面中通过this.param1的方式使用,param1为json中的参数名,param1对应的值会统一转换为String类型

示例:

router.replace({
uri: '/test'
params: {testId:'1'}
})

router.back()

返回上一页面

参数:

示例:

// A页面
router.push({
uri: 'B'
}) // B页面
router.push({
uri: 'C'
}) // C页面通过back,将返回B页面
router.back();
// B页面通过back,将返回A页面
router.back();

router.clear()

清空所有历史页面记录,仅保留当前页面

参数:

示例:

router.clear()

router.getLength()

获取当前页面栈的页面数量

返回值:

类型
说明
Number 页面数量

示例:

var length= router.getLength()
console.log("pages' length = "length);

router.getState()

获取当前页面状态

返回参数:

参数名
类型
说明
index Number 当前页面在页面栈中的位置
name String 当前页面的名称
path String 当前页面的路径

示例:

var page = router.getState()
console.log("page index = "+page.index);
console.log("page name = "+page.name);
console.log("page path = "+page.path);

快应用技术架构

快应用通过脚本来编写组件,安卓内部嵌入一个脚本解析引擎,将脚本转化为原生控件,通过编译生成rpk文件,应用调试器联系起脚本及安卓系统进行调试工作,具体流程图如下:

参考资料

 

Quikapp快应用开发入门的更多相关文章

  1. iOS开发入门教程

    iOS开发入门教程 http://my.oschina.net/mailzwj/blog/133273 摘要 iOS开发入门教程,从创建项目到运行项目,包括OC基础,调试,模拟器设置等相关知识. iO ...

  2. [安卓开发]App Widget开发入门指导

    本节所要讲的主要内容包括Android桌面小部件.App Widget的开发入门指导,并通过一个简单实例的形式来直观的讲解App Widget. 一.Widget .App Widget .Web A ...

  3. ArcGIS API for Silverlight开发入门

    你用上3G手机了吗?你可能会说,我就是喜欢用nokia1100,ABCDEFG跟我 都没关系.但你不能否认3G是一种趋势,最终我们每个人都会被包裹在3G网络中.1100也不是一成不变,没准哪天为了打击 ...

  4. React组件开发入门

    React 组件开发入门 Introduction 本文组成: Ryan Clark文章Getting started with React的翻译. 博主的实践心得. React由Facebook的程 ...

  5. (二)Hololens Unity 开发入门 之 Hello HoloLens~

    学习源于官方文档 微软官文~ 笔记一部分是直接翻译官方文档,部分各人理解不一致的和一些比较浅显的保留英文原文 (二)Hololens Unity 开发入门 之 Hello HoloLens~ 本文主要 ...

  6. 初学者福音——10个最佳APP开发入门在线学习网站

    根据Payscale的调查显示,现在的APP开发人员的年薪达到:$66,851.这也是为什么那么多初学的开发都想跻身到APP开发这行业的主要原因之一.每当你打开App Store时候,看着琳琅满目的A ...

  7. EOS开发入门

    EOS开发入门   在上一篇文章<EOS开发环境搭建>中,我们已经完成了EOS开发环境的搭建,本次为大家带来的是EOS开发入门的相关内容. 1. EOS的合约开发基础   智能合约是一种旨 ...

  8. sitecore开发入门Sitecore的CRUD操作 - 第二部分

    在上一篇(sitecore开发入门Sitecore的CRUD操作 - 第一部分)中我们讨论了如何使用Sitecore Item API,Glass,Fortis和Synthesis在Sitecore中 ...

  9. 【Arduino】开发入门【十】Arduino蓝牙模块与Android实现通信

    [Arduino]开发入门[十]蓝牙模块 首先show一下新入手的蓝牙模块 蓝牙参数特点 1.蓝牙核心模块使用HC-06从模块,引出接口包括VCC,GND,TXD,RXD,预留LED状态输出脚,单片机 ...

随机推荐

  1. ffmpeg结构体以及函数介绍(二)

    1 avcodec_find_decoder() /** * Find a registered decoder with a matching codec ID. * * @param id Cod ...

  2. 【html5】html学习笔记1

    html5语法规则 1.标签要小写 2.省略标签 如 <tr> <td> <tr><td> 3.属性不加” 如 <div  id=div1> ...

  3. stm32之keil开发环境搭建

    只要按照下面的一步步来,绝对能从0开始建立一个STM32工程.不仅包括工程建立过程,还有Jlink设置方法.本文使用芯片为STM32F103CB. 1 下载stm32F10x的官方库 http://w ...

  4. hi3531的h264压缩中修改波特率

    typedef struct hiVENC_ATTR_H264_CBR_S { HI_U32 u32Gop; HI_U32 u32StatTime; HI_U32 u32ViFrmRate; HI_F ...

  5. Hibernate中的条件查询完成类

    Hibernate中的条件查询有以下三个类完成: 1.Criteria:代表一次查询 2.Criterion:代表一个查询条件 3.Restrictions:产生查询条件的工具类

  6. org.apache.jasper.JasperException

    1.错误描述 2014-7-13 17:20:50 org.apache.catalina.core.StandardWrapperValve invoke 严重: Servlet.service() ...

  7. Flex报错之一

    1.错误描述 TypeError: Error #1009: 无法访问空对象引用的属性或方法. at com.gwtjs.components::DetailWindow/completeHandle ...

  8. C# 带偏移量自定义分页方法

    /// <summary> /// 带偏移量自定义分页方法 /// </summary> /// <param name="PageSize"> ...

  9. windows打开和关闭默认共享方法汇总

    Windows启动时都会默认打开admin$ ipc$ 和每个盘符的共享,对于不必要的默认共享,一般都会把它取消掉,可当又需要打开此默认共享时,又该从哪里设置呢.经过自己的验证,汇总出一下方法. 一: ...

  10. PyCharm运行报编码错误

    运行报如下错误: SyntaxError: Non-ASCII character '\xe8' in file /home/ubuntu/code/201803091253-text.py on l ...