Cordova系列之化繁为简:打造全场景适用的Cordova组件
前言
在我之前的文章 Cordova初探 的开篇中说到了Cordova在Android应用开发中的一个显著的局限性就是我们的Activity必须继承其提供的CordovaActivity。这种设计对于那些追求个性化UI设计的项目而言,显得尤为受限。
其实也可以理解,Cordova主要旨在为前端开发者提供一个方便的框架,让他们可以专注于编写HTML、CSS和JS代码,以满足业务需求,并确保这些代码能够在iOS和Android平台上运行。
这对于纯H5的开发方式来说是非常合适的,其中Cordova充当的是H5内容的容器。但是,在我遇到的项目中,纯H5开发方式实际上并不常见。
H5在理论上能够实现优雅的跨平台功能,但在实践中,我们需要考虑到多个关键因素,如兼容性、性能以及与原生应用交互的成熟度。
所以,本片文章主要是为Android开发的同学们提供一个基于Cordova封装的全场景适用的组件,使得我们在Cordova原有功能的基础上,能够突破必须继承CordovaActivity限制,能够随心所欲的自定义我们的ui,并提供更加丰富的功能以更好的支撑我们的业务需求。
目标 & 思路
目标
- 保持Cordova的原有功能和使用方式。
- 摆脱继承Activity的限制,使其像Webview控件一样灵活使用。
- 提供基于Activity和Fragment的封装,简化开发,同时支持UI个性化。
- 提供关键节点的回调,以满足更广泛的业务需求。
思路
以CordovaActivity这个入口源码为切入点,我们会发现,CordovaActivity代码量并不大,其核心功能集中在几个关键节点上,例如处理SplashScreen、加载配置、初始化变量、创建CordovaInterface等。所以,一个思路就是将CordovaActivity中关于CordovaWebView和CordovaWebViewEngine的核心代码提取出来,封装成一个更小粒度的自定义view供外部使用,同时提供Activity和Fragment的模版类,暴露出自定义ui的接口。这样一来,就可以实现核心目标了。
具体的代码实现已经上传到github上了,欢迎感兴趣的读者前往查看。下面主要详细介绍组件的使用方法。
组件使用
添加依赖
implementation("com.xeonyu:cordova-webcontainer:1.0.5")
继承CordovaWebContainerActivity使用
该方式适用于绝大部分业务场景,集成简单且保持UI灵活。
布局示例
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.yzq.demo.activity.WebContainerActivity"><!--标题栏--><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/purple_200"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:titleTextColor="@color/white" /><!--进度条--><ProgressBarandroid:id="@+id/progressbar"style="@android:style/Widget.ProgressBar.Horizontal"android:layout_width="match_parent"android:layout_height="5dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/toolbar" /><!--基于Cordova封装的web容器控件--><com.yzq.cordova_webcontainer.CordovaWebContainerandroid:id="@+id/web_container"android:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintTop_toBottomOf="@id/progressbar"app:layout_goneMarginTop="5dp" /><!--浮动按钮--><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/reload_fab"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="16dp"android:contentDescription="刷新"android:src="@drawable/refresh"android:tint="@color/white"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
代码示例:
/*** @description 继承自WebcontainerActivity的使用示例* @author yuzhiqiang (zhiqiang.yu.xeon@gmail.com)*/class WebContainerActivity : CordovaWebContainerActivity() {private lateinit var binding: ActivityWebContainerBindingprivate val TAG = "WebContainerActivity"/*布局初始化*/override fun initContentView() {binding = ActivityWebContainerBinding.inflate(layoutInflater)setContentView(binding.root)}/*初始化Webcontainer控件*/override fun initWebContainer(): CordovaWebContainer {with(binding) {webContainer.run {/*** 初始化webcontainer*/init(this@WebContainerActivity,this@WebContainerActivity)return binding.webContainer}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val url = "https://baidu.com/"binding.webContainer.loadUrl(url)binding.webContainer.setOnPageScrollChangedListener { xOffset, yOffset, oldxOffset, oldyOffset ->Log.i(TAG, "yOffset:$yOffset,oldyOffset:$oldyOffset")}binding.reloadFab.setOnClickListener {binding.webContainer.reload()}}}
在Fragment中使用
支持在Fragment中使用,继承CordovaWebContainerFragment
即可,api跟CordovaWebContainerActivity
一致。需要注意的是Fragment的宿主Activity需要处理下页面结果回调的方法。
宿主的Activity中重写下面两个方法,写法固定
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)currentFragment?.onActivityResult(requestCode, resultCode, data)}override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<out String>,grantResults: IntArray,) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)currentFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults)}
Fragment正常使用即可,示例代码
package com.yzq.demo.fragment/*** @description 在Fragment中使用示例* @author yuzhiqiang (zhiqiang.yu.xeon@gmail.com)*/class WebContainerFragment(val webUrl: String) : CordovaWebContainerFragment() {private lateinit var rootView: Viewprivate lateinit var webContainer: CordovaWebContaineroverride fun initContentView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?,): View {rootView = layoutInflater.inflate(R.layout.fragment_web_container, container, false)return rootView}override fun initWebContainer(): CordovaWebContainer {webContainer = rootView.findViewById(R.id.web_container)webContainer.init(requireActivity() as AppCompatActivity, this)return webContainer}override fun initWidget() {if (webUrl.isNotEmpty()) {webContainer.loadUrl(url = webUrl)} else {webContainer.loadUrl()}}}
作为自定义view使用
如果你不希望继承指定的Activity,你可以把CordovaWebContainer
作为自定义view使用。
package com.yzq.demo.activity/*** @description 直接使用Webcontainer控件的示例,适用于更加灵活的场景,例如你不想继承指定的Activity* @author yuzhiqiang (zhiqiang.yu.xeon@gmail.com)*/class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate val TAG = "MainActivity"override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.run {toolbar.title = "基于Cordova的webview使用"/*初始化*/webContainer.init(this@MainActivity, this@MainActivity)/*加载url*/
// val url = "https://www.baidu.com/"webContainer.loadUrl()}}//固定写法override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {super.onSaveInstanceState(outState, outPersistentState)binding.webContainer.onSaveInstanceState(outState)}//固定写法override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)binding.webContainer.onActivityResult(requestCode, resultCode, data)}//固定写法override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {binding.webContainer.startActivityForResult(requestCode)super.startActivityForResult(intent, requestCode, options)}
//固定写法override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<out String>,grantResults: IntArray,) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)binding.webContainer.onRequestPermissionsResult(requestCode, permissions, grantResults)}
}
API
列一下主要的api,具体的使用可以参考github上的示例代码。
初始化WebContainer
传入Activity以及LifecycleOwner
/*初始化(必须)*/
webContainer.init(this, this)
加载url
/*必须*/
webContainer.loadUrl(url)
关键事件回调
webContainer.addPagePbserver(PageObserver)
支持以下事件监听
请求拦截处理
/*可选拦截请求 等同于shouldInterceptRequest 记得用这个*/webContainer.webviewClient.interceptRequest { view, request, response ->val url = request.url.toString()Log.i(TAG, "interceptRequest:$url")return@interceptRequest response}
loadurl 处理
/*可选 处理准备load的url 等同于 shouldOverrideUrlLoading*/webContainer.webviewClient.overrideUrlLoading { view, request ->Log.i(TAG, "overrideUrlLoading:${request.url}")request.url.toString().let {if (it.startsWith("baidu://")) {return@overrideUrlLoading true}}return@overrideUrlLoading false}
滚动监听
webContainer.setOnPageScrollChangedListener { xOffset, yOffset, oldxOffset, oldyOffset ->Log.i(TAG, "yOffset:$yOffset,oldyOffset:$oldyOffset")}
其他使用跟webview api一样
混淆
组件内部已包含必要的混淆规则。
#Cordova
-keep class org.apache.cordova.**{*;}
-keep interface org.apache.cordova.**{*;}
-keep enum org.apache.cordova.**{*;}
这就是基于Cordova封装的全场景组件。如果您觉得本组件对您有所帮助,欢迎在GitHub上点个star,希望能帮到你。
github地址: cordova-webcontainer
感谢阅读,觉有有帮助点赞支持,如果有任何疑问或建议,欢迎在评论区留言。如需转载,请注明出处:喻志强的博客 ,谢谢!
相关文章:

Cordova系列之化繁为简:打造全场景适用的Cordova组件
前言 在我之前的文章 Cordova初探 的开篇中说到了Cordova在Android应用开发中的一个显著的局限性就是我们的Activity必须继承其提供的CordovaActivity。这种设计对于那些追求个性化UI设计的项目而言,显得尤为受限。 其实也可以理解,Cordova主要旨在为前…...
Flink之Catalog
Catalog Catalog概述Catalog分类 GenericInMemoryCatalogJdbcCatalog下载JAR包及使用重启操作创建Catalog查看与使用Catalog自动初始化catalog HiveCatalog下载JAR包及使用重启操作hive metastore服务创建Catalog查看与使用CatalogFlink与Hive中操作自动初始化catalog 用户自定…...

计算机网络——物理层-传输方式(串行传输、并行传输,同步传输、异步传输,单工、半双工和全双工通信)
目录 串行传输和并行传输 同步传输和异步传输 单工、半双工和全双工通信 串行传输和并行传输 串行传输是指数据是一个比特一个比特依次发送的。因此在发送端和接收端之间,只需要一条数据传输线路即可。 并行传输是指一次发送n个比特,而不是一个比特&…...

男科医院服务预约小程序的作用是什么
医院的需求度从来都很高,随着技术发展,不少科目随之衍生出新的医院的,比如男科医院、妇科医院等,这使得目标群体更加精准,同时也赋能用户可以快速享受到服务。 当然相应的男科医院在实际经营中也面临痛点:…...

有没有实时检测微信聊天图片的软件,只要微信收到了有二维码的图片就把它提取出来?
10-2 如果你有需要自动并且快速地把微信收到的二维码图片保存到指定文件夹的需求,那本文章非常适合你,本文章教你如何实现自动保存微信收到的二维码图片到你指定的文件夹中,助你快速扫码,比别人领先一步。 首先需要准备好的材料…...
core-site.xml,yarn-site.xml,hdfs-site.xml,mapred-site.xml配置
core-site.xml <?xml version"1.0" encoding"UTF-8"?> <?xml-stylesheet type"text/xsl" href"configuration.xsl"?> <!--Licensed under the Apache License, Version 2.0 (the "License");you may no…...

数据分析实战 | KNN算法——病例自动诊断分析
目录 一、数据及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 八、模型评价 九、模型调参 十、模型改进 十一、模型预测 一、数据及分析对象 CSV文件——“bc_data.csv” 数据集链接:https://dow…...

JS实现数据结构与算法
队列 1、普通队列 利用数组push和shif 就可以简单实现 2、利用链表的方式实现队列 class MyQueue {constructor(){this.head nullthis.tail nullthis.length 0}add(value){let node {value}if(this.length 0){this.head nodethis.tail node}else{this.tail.next no…...

计算机毕业设计 基于SpringBoot的驾校管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...
S7-1200PLC和SMART PLC开放式以太网通信(UDP双向通信)
S7-1200PLC的以太网通信UDP通信相关介绍还可以参考下面文章链接: 博途PLC开放式以太网通信TRCV_C指令应用编程(运动传感器UDP通信)-CSDN博客文章浏览阅读2.8k次。博途PLC开放式以太网通信TSENG_C指令应用,请参看下面的文章链接:博途PLC 1200/1500PLC开放式以太网通信TSEND_…...

作用域插槽slot-scope
一般用于组件封装,将使用props传入组件的数据再次调出来或者单纯调用组件中的数据。也可用于为组件某个部分自定义样式以及为某次使用组件自定义样式。 直接拿elementui的el-table举例: <template><el-table v-loading"loading&q…...
Redis学习笔记13:基于spring data redis及lua脚本list列表实现环形结构案例
工作过程中需要用到环形结构,确保环上的各个节点数据唯一,如果有新的不同数据到来,则将最早入环的数据移除,每次访问环形结构都自动刷新有效期;可以基于lua 的列表list结构来实现这一功能,lua脚本可以节省网…...
c# 将excel导入 sqlite
nuget 须要加载 EPPlus.Core ExcelDataReader ExcelDataReader.DataSet //需要引用的扩展 using ExcelDataReader; using ExcelPackage OfficeOpenXml.ExcelPackage; public static void CreateZhouPianChaTable(){string tbname "zhou_pian_cha1";//判断表是否存…...
KafkaConsumer 消费逻辑
版本:kafka-clients-2.0.1.jar 之前想写个插件修改 kafkaConsumer 消费者的逻辑,根据 header 过滤一些消息。于是需要了解一下 kafkaConsumer 具体是如何拉取消费消息的,确认在消费之前过滤掉消息是否会有影响。 下面是相关的源码࿰…...

scss 实用教程
变量 $ 定义变量 $link-color: blue;变量名可以与css中的属性名和选择器名称相同 使用变量 a {color: $link_color; }$highlight-border: 1px solid $link_color;中划线和下划线相互兼容,即中划线声明的变量可以使用下划线的方式引用,反之亦然。 $li…...
NO.304 二维区域和检索 - 矩阵不可变
题目 给定一个二维矩阵 matrix,以下类型的多个请求: 计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。 实现 NumMatrix 类: NumMatrix(int[][] matrix) 给定整数矩阵 …...
牛客---简单密码python
现在有一种密码变换算法。 九键手机键盘上的数字与字母的对应: 1--1, abc--2, def--3, ghi--4, jkl--5, mno--6, pqrs--7, tuv--8 wxyz--9, 0--0,把密码中出现的小写字母都变成九键键盘对应的数字,如:a 变成 2&#x…...

devops完整搭建教程(gitlab、jenkins、harbor、docker)
devops完整搭建教程(gitlab、jenkins、harbor、docker) 文章目录 devops完整搭建教程(gitlab、jenkins、harbor、docker)1.简介:2.工作流程:3.优缺点4.环境说明5.部署前准备工作5.1.所有主机永久关闭防火墙…...
页面上时间显示为数字 后端返回给前端 response java系统
有时候,在一个系统里,会看到,有的页面时间显示正常,有的页面时间显示成数字。像这样: "createTime": 1698706491000 这是因为出参没有做转换,直接将java.util.Date类型的数据返回给前端了。 返…...
idea怎么配置tomcat
要在IntelliJ IDEA中配置Tomcat,请按照以下步骤操作: 打开IntelliJ IDEA,点击File -> Settings(或者使用快捷键CtrlAltS)。 在设置窗口左侧导航栏中,选择Build, Execution, Deployment -> Applicati…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...

前端开发者常用网站
Can I use网站:一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use:Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站:MDN JavaScript权威网站:JavaScript | MDN...