TWA踩坑记-从零到一让你的博客变成app并上架商店

前言

在上一篇文章 PWA踩坑记-从零到一让你的博客也能离线访问 中,我介绍了如何将您的博客升级为PWA (Progressive-Web-App) 应用。

在这篇文章里,我将向您一步一步展示如何使您现有的PWA转化为TWA

您将学到:

  • 什么是TWA?
  • 什么是activity?
  • TWA特性
  • 安卓开发基础环境搭建
  • Gradle的基本概念
  • TWA与网站的双向验证方法
  • Android软件签名
  • 如何自动化把隐藏的静态资源复制到public文件夹?
  • 安卓软件的一般发布到应用市场流程

什么是TWA?

TWA全称为 Trusted Web Activities(被信任的网络应用)。简单来说,它的作用就是使您现有的PWA可以在任意设备上运行,不管有没有安装Chrome浏览器。

在将您的网站改造为TWA之前,您必须先按照上一篇文章 PWA踩坑记-从零到一让你的博客也能离线访问 将网站改造成PWA,然后才能进行接下来内容的学习。

这篇文站同样适用SPA(单页应用),和其它动态网站

在阅读下文前,请牢记,TWA就是一个activity

什么是 activity

activity ,既为安卓里面的一种“窗口”或者叫“页面”,每打开一个 app ,就是打开了一个 activity ,一般 app 内包含若干个 activity ,他们之间可以相互转跳,当你看到转跳动画的时候,很可能就是发生了 activity 的转跳。

activity 跳转

一个 app 含有0至多个 activity,一般情况下不会出现0个 activity 的 app,因为0个 activity 实际上意味着此 app 无用户交互页面。但是实际上,很大一部分的 app 是不需要与用户交互的,您可以翻看一下自己手机的应用列表里面的系统应用,您会发现,安装的应用数量相当庞大,但是自己主屏幕的图标数量却没那么多,就是因为很多 app 都是0个 activity 的原因。

以上说法可能不太准确,而且页面还可能是fragment,而不是activity,但是您只需要简单了解至此即可。

什么是 web activity

web activity 是相对于 webview 来说的,webview 传统情况下是app承载浏览器功能的一种实现。比如说在QQ里面点击了一个链接,就会打开一个类似于内置浏览器的页面来访问您所点击的链接。当然类似的 webview app 很多,比如:淘宝,京东,QQ,微信,小黑鱼,百度地图,网易云音乐等等。

浏览器、Custom-Tab、WebView 对比

传统 webview 的好处自然是其丰富的可定制化的特性,比如管理用户 cookie ,防止用户访问不安全的网站,强制启用安全访问等。

但是随之而来的问题就是:

  1. webview 的更新不是由用户来决定的,当 app 的 webview 过期时,对于用户的信息安全是非常危险的;

  2. app要集成一个webview进去,会造成应用体积暴增,导致用户出现抵触安装的情绪;

  3. 开发复杂,移动端工程师必须花费心思去维护webview模块,以保证其功能性。

所以 web activity 的概念是就是一个页面,他会替你在内部加载网页,模仿得让用户不太感觉的出来是一个网页。

TWA的特性

  1. **内容安全可信任。**用户打开后访问的内容一定是你认可的网站,你的网站不会被其他app所冒充。(后文将介绍如何让你的TWA和网站相互认证)

  2. 你的网页是什么样,用户打开TWA就是什么样,就像在普通的浏览器里面一样,只不过,它是全屏运行的。当然,TWA的先决条件就是你的网站首先能适应移动端的屏幕大小,或专门为移动端开发。

  3. TWA默认还是会使用chrome浏览器作为承载。但是不像『PWA踩坑记-从零到一让你的博客也能离线访问』那样,需要强制用户下载安装chrome浏览器才能正常使用。其他实现了TWA的浏览器同样可以作为您app里web内容的承载。

TWA

截止这篇文章发布的时候2019年3月23日,如果用户没有安装chrome或其他实现了TWA接口的浏览器,TWA会fallback到Custom Tab或用户手机的默认浏览器。

  1. 宿主app对您TWA内容没有直接的访问权限,它仅作为一个承载的作用。比如您不能通过app直接操作cookie或localStorage。如果您需要根据TWA定制化您的网页内容或显示形式,您可以通过url参数、自定义的请求/响应头(HTTP header)或intent URIs,比如您需要实现国际化等操作。

开发TWA

正如前文所说到,一个app至少包含0个activity,所以app是您开发任何activity的前置条件。

接下来我将向您展示如何创建一个安卓app项目来开发您的TWA。(您不需要擅长Java或Kotlin的知识)

以本地APP安装的TWA

卸载TWA时的系统提示

环境搭建

Android Studio

首先,您需要安装安卓集成开发环境(IDE):Android Studio

  1. 点击上面链接,下载对应版本的Android Studio,并安装,下文以PC为例:

点击 Download Android Studio

  1. 勾选同意条款,然后开始下载安装。

  2. 安装完毕之后,第一次打开会要求您安装SDK

选择最小化的SDK安装即可,推荐您使用真机进行调试,即不需要安装Simulation之类的虚拟机

开发过程

打开Android Studio,新建一个项目

Android Studio将会提示您选择一种预设activity,请在这里选择No activity,因为稍后引入的库会包含一个activity。

选择No activity

点击下一步,填写app基本信息

填写app信息

  • Name是您应用的名字
  • Package Name 一般以倒写的网址开头,是Java编码的即来习惯
  • Save Location 项目在磁盘中存放的位置
  • Minimum API Level 项请选择API Level 16 或以上,这是TWA支持的最低标准
  • 其他选项如图默认即可

点击Finish

等待Gradle脚本构建完成

Gradle脚本正在同步中

建议将文件显示方式更改为project,这样会以真实的文件系统存放方式显示您的文件夹。

在继续之前,您需要知晓,gradle是一种构建工具,就像npm+webpack(nodejs),maven(Java web)等其他包管理构建工具。

gradle项目可以做到模块化,在默认的安卓项目里,一个叫做app的模块就是您默认的模块,所以您的项目里应该有两个gradle脚本名字都是:build.gradle,在最外层的build.gradle是项目级别的配置文件,在app目录下的build.gradle是当前模块级别配置文件。

修改gradle文件

  1. 打开项目级别的build.gradle文件,添加一个源maven { url "https://jitpack.io" }
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
}

因为您改动了这些脚本,Android Studio 将会提示您 Sync Now,请点击同步。

  1. 在您app模块级别的build.gradle文件中添加一条依赖
implementation 'com.github.GoogleChrome.custom-tabs-client:customtabs:76d8d07'

关于custom-tabs-client的版本问题,请您前往https://github.com/GoogleChrome/custom-tabs-client查看最新Commit ID,如:

图中橘黄色圈起来的即为版本

如有必要请您自行替换版本号。

再次选择立即同步(Sync Now)

添加TWA Activity

导航到您App的app/src/main/AndroidManifest.xml文件

将如下activitytag添加到applicationtag里面

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.tellyouwhat.articles">

<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="@style/AppTheme">

<activity android:name="android.support.customtabs.trusted.LauncherActivity">

<!-- Edit android:value to change the url opened by the TWA -->
<meta-data
android:name="android.support.customtabs.trusted.DEFAULT_URL"
android:value="https://tellyouwhat.cn" />

<!-- This intent-filter adds the TWA to the Android Launcher -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<!--
This intent-filter allows the TWA to handle Intents to open
tellyouwhat.cn.
-->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<!-- Edit android:host to handle links to the target URL-->
<data
android:host="tellyouwhat.cn"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>
  1. meta-data标记告诉TWA哪个URL应该打开。android:value使用要打开的PWA的URL 更改属性。
  2. 第二个 intent-filter标签允许TWA被其他应用如浏览器打开。

从浏览器点击了tellyouwhat.cn域下资源跳转到TWA应用

全屏运行?

建立从app到网页的认证

app > res > values > strings.xml文件中添加以下声明:

<resources>
<string name="app_name">告你什么文集</string>
<string name="asset_statements">
[{
\"relation\": [\"delegate_permission/common.handle_all_urls\"],
\"target\": {
\"namespace\": \"web\",
\"site\": \"https://tellyouwhat.cn\"}
}]
</string>
</resources>

将其中的https://tellyouwhat.cn更改为您自己的网站。

返回到AndroidManifest.xml文件中,在activity tag之前,添加如下代码:

<meta-data
android:name="asset_statements"
android:resource="@string/asset_statements" />
建立从网页到app的认证

本文假设您使用的是hexo静态博客生成器

  1. 生成app的签名的证书文件

选择菜单栏上build菜单,选则generate signed bundle/apk,在弹出的窗口中选择apk

点击下一步

选择Create new

点击新建一个key

这里面的内容可以随便填一填,但是要注意这么几个细节:

  • 一个jks文件可以被多个app使用
  • 但是每个app都只能用唯一的别名(alias)
  • key的密码和alias的密码是分开的
  • 签名时这两个密码都需要,所以您应该记住自己设置的密码

继续点击next

选择V1签名,即可生成正式版app(右上角点击运行出现在手机上的app属于debug版本,非正式签名,是无法发不到应用商店的)

这样我们就得到了签名的证书文件

  1. 生成app links

在IDE的右侧边栏寻找Assistant

如果没有找到的话,请双击shift,在打开的面板里输入assistant

然后就能打开了。

点击第3条:open digital asset links file Generateor

确认域名和包名的正确性,然后选择keystore文件,点击生成一段json文本

  1. 将这段生成的文本复制下来
  2. 到您的hexo博客根目录下

还记得我在上篇文章中说到的您自己新建的static_files文件夹吗?

在这里它将继续发挥作用。

根据TWA的要求,您必须把刚才复制的那段json文本保存成/.well-known/assetlinks.json文件放在您的域名下,但是他是一个.开头的文件夹,hexo在生成博客的时候,会忽略掉source文件夹下以.开头的文件(夹)

在您以前在scripts文件夹下创建的event.js中添加如下代码,他的功能是把某文件夹下所有文件复制到最终生成的public文件夹下:

hexo.on('generateAfter', function () {
if (!fs.existsSync('./public')) {
// 如果缓存文件夹不存在就创建一个
fs.mkdirSync('./public')
}
copyDir('./static_files/copytopublic', './public', function (err) {
if (err) {
console.error(err);
}
})

fs.writeFile('./public/sw.js',
fs.readFileSync('./static_files/sw.js').toString().replace('{uniqueIdentifier}', new Date().toISOString()),
function (err) {
if (err) {
console.error(err)
} else {
console.log('service worker created')
}
})
})

/*
* 复制目录、子目录,及其中的文件
* @param src {String} 要复制的目录
* @param dist {String} 复制到目标目录
*/
function copyDir(src, dist, callback) {
fs.access(dist, function (err) {
if (err) {
// 目录不存在时创建目录
fs.mkdirSync(dist);
}
_copy(null, src, dist);
});

function _copy(err, src, dist) {
if (err) {
callback(err);
} else {
fs.readdir(src, function (err, paths) {
if (err) {
callback(err)
} else {
paths.forEach(function (path) {
var _src = src + '/' + path;
var _dist = dist + '/' + path;
fs.stat(_src, function (err, stat) {
if (err) {
callback(err);
} else {
// 判断是文件还是目录
if (stat.isFile()) {
fs.writeFileSync(_dist, fs.readFileSync(_src));
console.log('copying file', _src)
} else if (stat.isDirectory()) {
// 当是目录是,递归复制
copyDir(_src, _dist, callback)
}
}
})
})
}
})
}
}
}

该代码将会把您./static_files/copytopublic文件夹下的所有文件复制到最终的public文件夹中。

下一步我们在./static_files/copytopublic文件夹下创建/.well-known/assetlinks.json文件,并把Android Studio Assistant生成的json文本填充进去。

这时候,执行hexo g的时候,就会在public文件夹看到/.well-known/assetlinks.json文件。

但是,请注意hexo d的时候依然不会把文件最终发布到目的地。

接下来,请打开hexo博客的_config.yml文件,在您的deploy项中添加ignore_hidden属性:

deploy:
- type: git
repo: git@somegit.com:yourgit.git
ignore_hidden: false

完美解决hexo博客的问题。

测试

在您成功部署之后(如有CDN,您可能需要刷新一下缓存),返回到Android Studio 的 Assistant,往下翻,下方有一个验证的工具,点击之后,如果通过了,就会有两个绿色亮起。

测试通过

更换应用图标

在您项目的app\src\main\res\文件夹下,有好几个mipmap开头的文件夹,他们是分别针对不同屏幕尺寸的图标,简单的,您只需将其中的png文件替换即可。

图标文件

为此您可能需要裁切很多分辨率的图标文件:

不同尺寸的图标文件

完整代码

您可以在这里找到实例的完整代码:https://github.com/HarborZeng/tellyouwhat-app

发布应用

这篇文章将会以发布到酷安为例,为您介绍如何将app发布到应用市场。

生成release版本app

正如前文所提到过的,在build菜单项,选择generate signed apk,下一步,选择V1签名release配置文件,即可生成正式版app。

正式版app存放路径

使用酷安应用市场

  1. 首先您需要注册酷安账号,这里省略。
  2. 然后前往https://developer.coolapk.com/认证为开发者,这里省略。
  3. 点击添加新应用。
  4. 输入您应用的信息输入您应用的信息
  5. 上传图标,输入简介,添加截图等
  6. 上传apk文件,通过后即可发布上传apk文件

发布完成

参考资料


   转载规则


《TWA踩坑记-从零到一让你的博客变成app并上架商店》 Harbor Zeng 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
在JavaScript中元素id和name可以当做全局变量直接调用 在JavaScript中元素id和name可以当做全局变量直接调用
HTML标准API之异端邪说:id和name可以不用getElementBy和querySelector(all)即可直接当做全局变量使用。 现象 偶然一次浏览到别人的项目,发现https://github.com/zjcqoo/zjcqoo.github.io他的html代码写法好奇怪,直接把html里面标签的id当做变量来使用。 <!doctype html><html&g
下一篇 
如何更新CircleCI的缓存 如何更新CircleCI的缓存
前言 众所周知,CircleCI的缓存是不可变的(immutable),如果不跟后缀保存缓存的话,会报错如下: Skipping cache generation, cache already exists for key 如何解决这样的错误,也就是如何更新现有的缓存,是一个急需解决的问题。 现状 在初始构建运行之后,有了缓存,未来的构建将运行得更快。 common case steps:
  目录