-
2022-01-04 17:46:26
本文节选自霍格沃兹测试学院内部教材
PageObject 的核心思想是六大原则,掌握六大原则才可以进行 PageObject 实战演练,这是 PageObject 的精髓所在。Selenium 官方凝聚出六大原则,后面的 PageObject 使用都将围绕六大原则开展:
- 公共方法代表页面提供的服务
- 不要暴露页面细节
- 不要把断言和操作细节混用
- 方法可以 return 到新打开的页面
- 不要把整页内容都放到 PO 中
- 相同的行为会产生不同的结果,可以封装不同结果
下面,对上述六大原则进行解释:
- 原则一:要封装页面中的功能(或者服务),比如点击页面中的元素,进入到新的页面,就可以为这个服务封装方法“进入新页面”。
- 原则二:封装细节,对外只提供方法名(或者接口)。
- 原则三:封装的操作细节中不要使用断言,把断言放到单独的模块中,比如 testcase。
- 原则四:点击一个按钮会开启新的页面,可以用 return 方法表示跳转,比如
return MainPage()
表示跳转到新的 PO:即 MainPage。 - 原则五:只为页面中重要的元素进行 PO 设计,舍弃不重要的内容。
- 原则六:一个动作可能产生不同结果,比如点击按钮后,可能点击成功,也可能点击失败,为两种结果封装两个方法,
click_success
和click_error
。
案例
以企业微信首页为例,企业微信首页有二个主要功能:立即注册和企业登录。
企业微信网址:https://work.weixin.qq.com/
Index 页面
点击企业登录可以进入登录页面,在页面可以扫码登录和企业注册。
Login 页面
点击企业注册可以进入注册页面,在页面可以输入相关信息进行注册。
Register 页面
用 PageObject 原则为页面建模,这里涉及三个页面:首页,登录,注册。在代码中创建对应的三个类 Index、Login、Register:
- 登陆页⾯提供 login findPassword 功能
- Login 类 + login findPassword ⽅法
- 登录页⾯内的元素有多少并不关⼼,隐藏内部界⾯控件
- 登录成功和失败会分别返回不同的页⾯
- findPassword
- loginSuccess
- loginFail
- 通过⽅法返回值判断登录是否符合预期
UML图
代码
目录结构
test_selenium │ ├─page │ base_page.py │ index.py │ login.py │ register.py │ __init__.py │ └─testcase test_index.py __init__.py
BasePage 是所有 PageObject 的父类,它为子类提供公共的方法,比如下面的 BasePage 提供初始化 driver 和退出
driver,代码中在 base_page 模块的 BasePage 类中使用__init__
初始方法进行初始化操作,包括 driver
的复用,driver 的赋值,全局等待的设置(隐式等待)等等。
from time import sleepfrom selenium import webdriverfrom selenium.webdriver.remote.webdriver import WebDriver class BasePage: def __init__(self, driver: WebDriver = None): #此处对driver进行复用,如果不存在driver,就构造一个新的 if driver is None: # Index页面需要用,首次使用时构造新driver self._driver = webdriver.Chrome() # 设置隐式等待时间 self._driver.implicitly_wait(3) # 访问网页 self._driver.get(self._base_url) else: # Login与Register等页面需要用这个方法,避免重复构造driver self._driver = driver def close(self): sleep(20) self._driver.quit() Index 是企业微信首页的 PageObject,它存在两个方法,进入注册 PageObject 和进入登陆 PageObject,这里 return 方法返回 PageObject 实现了页面跳转。比如:goto_register 方法 return Register,实现从首页跳转到注册页。
from selenium.webdriver.common.by import By from test_selenium.page.base_page import BasePagefrom test_selenium.page.login import Loginfrom test_selenium.page.register import Register class Index(BasePage): _base_url = "https://work.weixin.qq.com/" # 进入注册页面 def goto_register(self): self._driver.find_element(By.LINK_TEXT, "立即注册").click() # 创建Register实例后,可调用Register中的方法 return Register(self._driver) # 进入登录页面 def goto_login(self): self._driver.find_element(By.LINK_TEXT, "企业登录").click() # 创建Login实例后,可调用Login中的方法 return Login(self._driver)
Login 是登录页面的 PageObject,主要功能有:进入注册页面,扫描二维码,因此创建两个方法代表两个功能:scan_qrcode 和
goto_registry。代码跟上面相似,不过多介绍。
from selenium.webdriver.common.by import Byfrom test_selenium.page.base_page import BasePagefrom test_selenium.page.register import Register class Login(BasePage): # 扫描二维码 def scan_qrcode(self): pass # 进入注册页面 def goto_registry(self): self._driver.find_element(By.LINK_TEXT, "企业注册").click() return Register(self._driver)
Register 是注册页面的 PageObject,主要功能是填写正确注册信息,当填写错误时,返回错误信息。register
方法实现了正确的表格填写,当填写完毕时返回自身(页面还停留在注册页)。get_error_message
方法实现了错误填写的情况,如果填写错误,就收集错误内容并返回。
from selenium.webdriver.common.by import Byfrom test_selenium.page.base_page import BasePage class Register(BasePage): # 填写注册信息,此处只填写了部分信息,并没有填写完全 def register(self, corpname): # 进行表格填写 self._driver.find_element(By.ID, "corp_name").send_keys(corpname) self._driver.find_element(By.ID, "submit_btn").click() # 填写完毕,停留在注册页,可继续调用Register内的方法 return self #填写错误时,返回错误信息 def get_error_message(self): # 收集错误信息并返回 result=[] for element in self._driver.find_elements(By.CSS_SELECTOR, ".js_error_msg"): result.append(element.text) return result
test_index 模块是对上述功能的测试,它独立于 page 类,在 TestIndex 类中只需要调用 page
类提供的方法即可,比如下面对注册页及登陆页的测试使用了 test_register 和 test_login 方法:
from test_selenium.page.index import Index class TestIndex: # 所有步骤前的初始化 def setup(self): self.index = Index() # 对注册功能的测试 def test_register(self): # 进入index,然后进入注册页填写信息 self.index.goto_register().register("霍格沃兹测试学院") # 对login功能的测试 def test_login(self): # 从首页进入到注册页 register_page = self.index.goto_login().goto_registry()\ .register("测吧(北京)科技有限公司") # 对填写结果进行断言,是否填写成功或者填写失败 assert "请选择" in "|".join(register_page.get_error_message()) # 关闭driver def teardown(self): self.index.close()
Web自动化中PageObject 原则就先讲到这里啦,大家还想看什么内容的文章也可以留言告诉我们哦!
** _
来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力
QQ交流群:484590337
公众号 TestingStudio
视频资料领取:https://qrcode.testing-studio.com/f?from=CSDN&url=https://ceshiren.com/t/topic/15844
点击查看更多信息更多相关内容 -
PageOffice for Java (免费破解版)文档控件(官网版本)
2014-03-08 10:13:34PageOffice实现了在线编辑保存Word、Excel、PPT、WPS等Office文档的基本功能,对于简单的在线Office办公、追踪Word修订痕迹、全文检索的实现已经绰绰有余。 PageOffice是市场上唯一一款能够同时支持IE、谷歌Chrome... -
Linux 内存管理窥探(5):page 数据结构
2019-02-12 15:30:31前面聊过内存的表示由 node -&... page ,聊聊 page 结构。 内核把物理页作为内存管理的基本单位. 尽管处理器的最小可寻址单位通常是字, 但是, 内存管理单元MMU通常以页为单位进行处理. 因此,从虚拟内存的上...本文参考:https://blog.csdn.net/gatieme/article/details/52384636 感谢作者无私的奉献
前面聊过内存的表示由 node -> zone -> page ,聊聊 page 结构。
内核把物理页作为内存管理的基本单位. 尽管处理器的最小可寻址单位通常是字, 但是, 内存管理单元MMU通常以页为单位进行处理. 因此,从虚拟内存的上来看,页就是最小单位.
页帧代表了系统内存的最小单位, 对内存中的每个页都会创建struct page的一个实例. 内核必须要保证page结构体足够的小,否则仅struct page就要占用大量的内存.
因为即使在中等程序的内存配置下, 系统的内存同样会分解为大量的页. 例如, IA-32系统中标准页长度为4KB, 在内存大小为384MB时, 大约有100000页. 就当今的标准而言, 这个容量算不上很大, 但页的数目已经非常可观了
因而出于节省内存的考虑,内核要尽力保持struct page尽可能的小. 在典型的系统中, 由于页的数目巨大, 因此对page结构的小改动, 也可能导致保存所有page实例所需的物理内存暴涨.
页的广泛使用, 增加了保持结构长度的难度 : 内存管理的许多部分都使用页, 用于各种不同的用途. 内核的一部分可能完全依赖于struct page提供的特定信息, 而这部分信息堆内核的其他部分页可能是完全无用的. 等等.
1. struct page结构
内核用 struct page 来代表每一个物理页面(32bit CPU 上通常是 4K 一个 page)
出于节省内存的考虑,struct page 中使用了大量的联合体union
/* * Each physical page in the system has a struct page associated with * it to keep track of whatever it is we are using the page for at the * moment. Note that we have no way to track which tasks are using * a page, though if it is a pagecache page, rmap structures can tell us * who is mapping it. * * The objects in struct page are organized in double word blocks in * order to allows us to use atomic double word operations on portions * of struct page. That is currently only used by slub but the arrangement * allows the use of atomic double word operations on the flags/mapping * and lru list pointers also. */ struct page { /* First double word block */ unsigned long flags; /* Atomic flags, some possibly updated asynchronously 描述page的状态和其他信息 */ union { struct address_space *mapping; /* If low bit clear, points to * inode address_space, or NULL. * If page mapped as anonymous * memory, low bit is set, and * it points to anon_vma object: * see PAGE_MAPPING_ANON below. */ void *s_mem; /* slab first object */ atomic_t compound_mapcount; /* first tail page */ /* page_deferred_list().next -- second tail page */ }; /* Second double word */ struct { union { pgoff_t index; /* Our offset within mapping. 在映射的虚拟空间(vma_area)内的偏移; 一个文件可能只映射一部分,假设映射了1M的空间, index指的是在1M空间内的偏移,而不是在整个文件内的偏移。 */ void *freelist; /* sl[aou]b first free object */ /* page_deferred_list().prev -- second tail page */ }; union { #if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \ defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE) /* Used for cmpxchg_double in slub */ unsigned long counters; #else /* * Keep _refcount separate from slub cmpxchg_double * data. As the rest of the double word is protected by * slab_lock but _refcount is not. */ unsigned counters; #endif struct { union { /* * Count of ptes mapped in mms, to show * when page is mapped & limit reverse * map searches. * 页映射计数器 */ atomic_t _mapcount; struct { /* SLUB */ unsigned inuse:16; unsigned objects:15; unsigned frozen:1; }; int units; /* SLOB */ }; /* * Usage count, *USE WRAPPER FUNCTION* * when manual accounting. See page_ref.h * 页引用计数器 */ atomic_t _refcount; }; unsigned int active; /* SLAB */ }; }; /* * Third double word block * * WARNING: bit 0 of the first word encode PageTail(). That means * the rest users of the storage space MUST NOT use the bit to * avoid collision and false-positive PageTail(). */ union { struct list_head lru; /* Pageout list, eg. active_list * protected by zone->lru_lock ! * Can be used as a generic list * by the page owner. */ struct dev_pagemap *pgmap; /* ZONE_DEVICE pages are never on an * lru or handled by a slab * allocator, this points to the * hosting device page map. */ struct { /* slub per cpu partial pages */ struct page *next; /* Next partial slab */ #ifdef CONFIG_64BIT int pages; /* Nr of partial slabs left */ int pobjects; /* Approximate # of objects */ #else short int pages; short int pobjects; #endif }; struct rcu_head rcu_head; /* Used by SLAB * when destroying via RCU */ /* Tail pages of compound page */ struct { unsigned long compound_head; /* If bit zero is set */ /* First tail page only */ #ifdef CONFIG_64BIT /* * On 64 bit system we have enough space in struct page * to encode compound_dtor and compound_order with * unsigned int. It can help compiler generate better or * smaller code on some archtectures. */ unsigned int compound_dtor; unsigned int compound_order; #else unsigned short int compound_dtor; unsigned short int compound_order; #endif }; #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKS struct { unsigned long __pad; /* do not overlay pmd_huge_pte * with compound_head to avoid * possible bit 0 collision. */ pgtable_t pmd_huge_pte; /* protected by page->ptl */ }; #endif }; /* Remainder is not double word aligned */ union { unsigned long private; /* Mapping-private opaque data: * usually used for buffer_heads * if PagePrivate set; used for * swp_entry_t if PageSwapCache; * indicates order in the buddy * system if PG_buddy is set. * 私有数据指针,由应用场景确定其具体的含义 */ #if USE_SPLIT_PTE_PTLOCKS #if ALLOC_SPLIT_PTLOCKS spinlock_t *ptl; #else spinlock_t ptl; #endif #endif struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */ }; #ifdef CONFIG_MEMCG struct mem_cgroup *mem_cgroup; #endif /* * On machines where all RAM is mapped into kernel address space, * we can simply calculate the virtual address. On machines with * highmem some memory is mapped into kernel virtual memory * dynamically, so we need a place to store that address. * Note that this field could be 16 bits on x86 ... ;) * * Architectures with slow multiplication can define * WANT_PAGE_VIRTUAL in asm/page.h */ #if defined(WANT_PAGE_VIRTUAL) void *virtual; /* Kernel virtual address (NULL if not kmapped, ie. highmem) */ #endif /* WANT_PAGE_VIRTUAL */ #ifdef CONFIG_KMEMCHECK /* * kmemcheck wants to track the status of each byte in a page; this * is a pointer to such a status block. NULL if not tracked. */ void *shadow; #endif #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS int _last_cpupid; #endif } /* * The struct page can be forced to be double word aligned so that atomic ops * on double words work. The SLUB allocator can make use of such a feature. */ #ifdef CONFIG_HAVE_ALIGNED_STRUCT_PAGE __aligned(2 * sizeof(unsigned long)) #endif ;
字段 描述 flag 用来存放页的状态,每一位代表一种状态,所以至少可以同时表示出32中不同的状态,这些状态定义在linux/page-flags.h中 virtual 对于如果物理内存可以直接映射内核的系统, 我们可以之间映射出虚拟地址与物理地址的管理, 但是对于需要使用高端内存区域的页, 即无法直接映射到内核的虚拟地址空间, 因此需要用virtual保存该页的虚拟地址 _refcount 引用计数,表示内核中引用该page的次数, 如果要操作该page, 引用计数会+1, 操作完成-1. 当该值为0时, 表示没有引用该page的位置,所以该page可以被解除映射,这往往在内存回收时是有用的 _mapcount 被页表映射的次数,也就是说该page同时被多少个进程共享。初始值为-1,如果只被一个进程的页表映射了,该值为0. 如果该page处于伙伴系统中,该值为PAGE_BUDDY_MAPCOUNT_VALUE(-128),内核通过判断该值是否为PAGE_BUDDY_MAPCOUNT_VALUE来确定该page是否属于伙伴系统 index 在映射的虚拟空间(vma_area)内的偏移;一个文件可能只映射一部分,假设映射了1M的空间,index指的是在1M空间内的偏移,而不是在整个文件内的偏移 private 私有数据指针,由应用场景确定其具体的含义 lru 链表头,用于在各种链表上维护该页, 以便于按页将不同类别分组, 主要有3个用途: 伙伴算法, slab分配器, 被用户态使用或被当做页缓存使用 mapping 指向与该页相关的address_space对象 注意区分_count 和 _mapcount,_mapcount 表示的是映射次数,而 _count 表示的是使用次数;被映射了不一定在使用,但要使用必须先映射。
2. mapping & index
mapping指定了页帧所在的地址空间, index是页帧在映射内部的偏移量. 地址空间是一个非常一般的概念. 例如, 可以用在向内存读取文件时. 地址空间用于将文件的内容与装载数据的内存区关联起来. mapping不仅能够保存一个指针, 而且还能包含一些额外的信息, 用于判断页是否属于未关联到地址空间的某个匿名内存区.
如果mapping = 0,说明该page属于交换高速缓存页(swap cache);当需要使用地址空间时会指定交换分区的地址空间swapper_space。
如果mapping != 0,第0位bit[0] = 0,说明该page属于页缓存或文件映射,mapping指向文件的地址空间address_space。
如果mapping != 0,第0位bit[0] != 0,说明该page为匿名映射,mapping指向struct anon_vma对象。
通过mapping恢复anon_vma的方法:anon_vma = (struct anon_vma *)(mapping - PAGE_MAPPING_ANON)。
pgoff_t index是该页描述结构在地址空间radix树page_tree中的对象索引号即页号, 表示该页在vm_file中的偏移页数, 其类型pgoff_t被定义为unsigned long即一个机器字长.
3. private 私有数据指针
private私有数据指针, 由应用场景确定其具体的含义:
如果设置了PG_private标志,则private字段指向struct buffer_head
如果设置了PG_compound,则指向struct page
如果设置了PG_swapcache标志,private存储了该page在交换分区中对应的位置信息swp_entry_t。
如果_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE,说明该page位于伙伴系统,private存储该伙伴的阶
4. lru链表头
最近、最久未使用struct slab结构指针变量
lru:链表头,主要有3个用途:
则page处于伙伴系统中时,用于链接相同阶的伙伴(只使用伙伴中的第一个page的lru即可达到目的)。
设置PG_slab, 则page属于slab,page->lru.next指向page驻留的的缓存的管理结构,page->lru.prec指向保存该page的slab的管理结构。
page被用户态使用或被当做页缓存使用时,用于将该page连入zone中相应的lru链表,供内存回收时使用。
5. 体系结构无关的页面的状态 flags
页的不同属性通过一系列页标志描述, 存储在struct page的flag成员中的各个比特位。
struct page { /* First double word block */ unsigned long flags; /* Atomic flags, some possibly updated asynchronously, 描述page的状态和其他信息 */
5.1 页面到管理区和节点的映射
static inline struct zone *page_zone(const struct page *page) { return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)]; } static inline void set_page_zone(struct page *page, enum zone_type zone) { page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT); page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT; } static inline void set_page_node(struct page *page, unsigned long node) { page->flags &= ~(NODES_MASK << NODES_PGSHIFT); page->flags |= (node & NODES_MASK) << NODES_PGSHIFT; }
其中NODE_DATA使用了全局的node表进行索引.
在UMA结构的机器中, 只有一个node结点即contig_page_data, 此时NODE_DATA直接指向了全局的contig_page_data, 而与node的编号nid无关,
那么page的flags标识主要分为4部分,其中标志位flag向高位增长, 其余位字段向低位增长,中间存在空闲位
字段 描述 section 主要用于稀疏内存模型SPARSEMEM,可忽略 node NUMA节点号, 标识该page属于哪一个节点 zone 内存域标志,标识该page属于哪一个zone flag page的状态标识 其中最后一个flag用于标识page的状态, 这些状态由枚举常量,由 enum pageflags 定义,在 include/linux/page-flags.h
enum pageflags { PG_locked, /* Page is locked. Don't touch. */ PG_error, PG_referenced, PG_uptodate, PG_dirty, PG_lru, PG_active, PG_slab, PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/ PG_arch_1, PG_reserved, PG_private, /* If pagecache, has fs-private data */ PG_private_2, /* If pagecache, has fs aux data */ PG_writeback, /* Page is under writeback */ PG_head, /* A head page */ PG_swapcache, /* Swap page: swp_entry_t in private */ PG_mappedtodisk, /* Has blocks allocated on-disk */ PG_reclaim, /* To be reclaimed asap */ PG_swapbacked, /* Page is backed by RAM/swap */ PG_unevictable, /* Page is "unevictable" */ #ifdef CONFIG_MMU PG_mlocked, /* Page is vma mlocked */ #endif #ifdef CONFIG_ARCH_USES_PG_UNCACHED PG_uncached, /* Page has been mapped as uncached */ #endif #ifdef CONFIG_MEMORY_FAILURE PG_hwpoison, /* hardware poisoned page. Don't touch */ #endif #if defined(CONFIG_IDLE_PAGE_TRACKING) && defined(CONFIG_64BIT) PG_young, PG_idle, #endif __NR_PAGEFLAGS, /* Filesystems */ PG_checked = PG_owner_priv_1, /* Two page bits are conscripted by FS-Cache to maintain local caching * state. These bits are set on pages belonging to the netfs's inodes * when those inodes are being locally cached. */ PG_fscache = PG_private_2, /* page backed by cache */ /* XEN */ /* Pinned in Xen as a read-only pagetable page. */ PG_pinned = PG_owner_priv_1, /* Pinned as part of domain save (see xen_mm_pin_all()). */ PG_savepinned = PG_dirty, /* Has a grant mapping of another (foreign) domain's page. */ PG_foreign = PG_owner_priv_1, /* SLOB */ PG_slob_free = PG_private, /* Compound pages. Stored in first tail page's flags */ PG_double_map = PG_private_2, };
页面状态 描述 PG_locked 指定了页是否被锁定, 如果该比特未被置位, 说明有使用者正在操作该page, 则内核的其他部分不允许访问该页, 这可以防止内存管理出现竞态条件 PG_error 如果涉及该page的I/O操作发生了错误, 则该位被设置 PG_referenced 表示page刚刚被访问过 PG_uptodate 表示page的数据已经与后备存储器是同步的, 即页的数据已经从块设备读取,且没有出错,数据是最新的 PG_dirty 与后备存储器中的数据相比,该page的内容已经被修改. 出于性能能的考虑,页并不在每次改变后立即回写, 因此内核需要使用该标识来表明页面中的数据已经改变, 应该在稍后刷出 PG_lru 表示该page处于LRU链表上, 这有助于实现页面的回收和切换. 内核使用两个最近最少使用(least recently used-LRU)链表来区别活动和不活动页. 如果页在其中一个链表中, 则该位被设置 PG_active page处于inactive LRU链表, PG_active和PG_referenced一起控制该page的活跃程度,这在内存回收时将会非常有用,当位于LRU active_list链表上的页面该位被设置, 并在页面移除时清除该位, 它标记了页面是否处于活动状态 PG_slab 该page属于slab分配器 PG_onwer_priv_1 PG_arch_1 直接从代码中引用, PG_arch_1是一个体系结构相关的页面状态位, 一般的代码保证了在第一次禁图页面高速缓存时, 该位被清除. 这使得体系结构可以延迟到页面被某个进程映射后, 才可以D-Cache刷盘 PG_reserved 设置该标志,防止该page被交换到swap PG_private 如果page中的private成员非空,则需要设置该标志, 用于I/O的页可使用该字段将页细分为多核缓冲区 PG_private_2 PG_writeback page中的数据正在被回写到后备存储器 PG_head PG_swapcache 表示该page处于swap cache中 PG_mappedtodisk 表示page中的数据在后备存储器中有对应 PG_reclaim 表示该page要被回收。当PFRA决定要回收某个page后,需要设置该标志 PG_swapbacked 该page的后备存储器是swap PG_unevictable 该page被锁住,不能交换,并会出现在LRU_UNEVICTABLE链表中,它包括的几种page:ramdisk或ramfs使用的页, shm_locked、mlock锁定的页 PG_mlocked 该page在vma中被锁定,一般是通过系统调用mlock()锁定了一段内存 6. 全局页面数组 mem_map
mem_map 是一个struct page的数组,管理着系统中所有的物理内存页面。在系统启动的过程中,创建和分配mem_map的内存区域.
UMA体系结构中, free_area_init 函数在系统唯一的 struct node 对象 contig_page_data 中 node_mem_map 成员赋值给全局的mem_map 变量
void __init free_area_init(unsigned long *zones_size) { zero_resv_unavail(); free_area_init_node(0, zones_size, __pa(PAGE_OFFSET) >> PAGE_SHIFT, NULL); }
void __init free_area_init_node(int nid, unsigned long *zones_size, unsigned long node_start_pfn, unsigned long *zholes_size) { pg_data_t *pgdat = NODE_DATA(nid); unsigned long start_pfn = 0; unsigned long end_pfn = 0; ..... alloc_node_mem_map(pgdat); pgdat_set_deferred_range(pgdat); ..... }
static void __ref alloc_node_mem_map(struct pglist_data *pgdat) { unsigned long __maybe_unused start = 0; unsigned long __maybe_unused offset = 0; #ifndef CONFIG_NEED_MULTIPLE_NODES /* * With no DISCONTIG, the global mem_map is just set as node 0's */ if (pgdat == NODE_DATA(0)) { mem_map = NODE_DATA(0)->node_mem_map; #if defined(CONFIG_HAVE_MEMBLOCK_NODE_MAP) || defined(CONFIG_FLATMEM) if (page_to_pfn(mem_map) != pgdat->node_start_pfn) mem_map -= offset; #endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */ } #endif }
-
SpringBoot(30) 整合PageOffice实现在线编辑Word和Excel
2020-07-15 17:07:47一、前言 PageOffice官网: http://www.zhuozhengsoft.com/ PageOffice集成说明: https://www.kancloud.cn/pageoffice_course_group/pageoffice_course/652260 准备集成环境 下载jar依赖:...将下载的jar依赖放进项目的...一、前言
- PageOffice官网: http://www.zhuozhengsoft.com/
- PageOffice集成说明: https://www.kancloud.cn/pageoffice_course_group/pageoffice_course/652260
准备集成环境
下载jar依赖:http://www.zhuozhengsoft.com/dowm/
二、SpringBoot整合PageOffice实现在线编辑Word和Excel
本文基于springboot
2.3.1.RELEASE
版本集成pageoffice1、
pom.xml
中新增相关依赖将下载的jar依赖放进项目的lib包下 ( 注:这里也可以自定义存放位置,然后修改本地引入位置
${project.basedir}/lib/pageoffice4.6.0.4.jar
即可 )
<!-- =================================== ↓↓↓↓↓↓ PageOffice所需依赖 start ↓↓↓↓↓↓ ==================================== --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- 添加Sqlite依赖(可选:如果不需要使用印章功能的话,不需要添加此依赖)--> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.7.2</version> </dependency> <!-- 添加PageOffice依赖(必须) --> <dependency> <groupId>com.zhuozheng</groupId> <artifactId>pageoffice</artifactId> <version>4.6.0.4</version> <scope>system</scope> <systemPath>${project.basedir}/lib/pageoffice4.6.0.4.jar</systemPath> </dependency> <!-- =================================== ↑↑↑↑↑↑ PageOffice所需依赖 end ↑↑↑↑↑↑ ==================================== -->
本地jar包引入需再新增如下,可参考文末源码demo
2、
application.yml
中新增pageoffice配置spring: # THYMELEAF (ThymeleafAutoConfiguration) thymeleaf: prefix: classpath:/templates/ suffix: .html cache: false # ======================== ↓↓↓↓↓↓ PageOffice相关配置 ↓↓↓↓↓↓ =============================== pageoffice: # 本地文件所在磁盘位置 docpath: /pageoffice/doc/ # 设置PageOffice自带印章管理程序的登录密码 popassword: 123456 # 指定一个磁盘目录用来存放PageOffice注册成功之后生成的license.lic文件 posyspath: /pageoffice/lic/
3、
resources/templates
下新增如下3个文件① Excel.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>打开Excel文件</title> <script type="text/javascript"> function Save() { document.getElementById("PageOfficeCtrl1").WebSave(); } </script> <script type="text/javascript"> function AddSeal() { try { document.getElementById("PageOfficeCtrl1").ZoomSeal.AddSeal(); } catch (e) { } } </script> </head> <body> <div style="width:auto;height:700px;" th:utext="${pageoffice}"></div> </body> </html>
② Word.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>打开Word文件</title> <script type="text/javascript"> function Save() { document.getElementById("PageOfficeCtrl1").WebSave(); } </script> <script type="text/javascript"> function AddSeal() { try { document.getElementById("PageOfficeCtrl1").ZoomSeal.AddSeal(); } catch (e) { } } </script> </head> <body> <div style="width:auto;height:700px;" th:utext="${pageoffice}"></div> </body> </html>
③ index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>HelloWorld</title> <style> #main { width: 15%; text-align: center; } ol { text-align: left; border: solid 1px gray; " } ol li { text-align: left; border-bottom: dotted 1px gray; height: 30px; margin-top: 10px; } </style> <!-- 引用后端项目中的pageoffice.js文件 --> <script type="text/javascript" src="http://localhost:8080/pageoffice.js"></script> </head> <body> <div id="main"> <h3>HelloWorld</h3> <ol> <li> <!-- openWindowModeless用法参考:http://www.zhuozhengsoft.com/help/js3/pobrowser/function/openwindowmodeless.htm --> <a href="javascript:POBrowser.openWindowModeless('http://localhost:8080/api/word','width=1200px;height=800px;');">打开Word文档</a> </li> <li> <a href="javascript:POBrowser.openWindowModeless('http://localhost:8080/api/excel','width=1200px;height=800px;');">打开Excel文档</a> </li> </ol> </div> </body> </html>
4、全局常用变量
public class Constants { /** * 项目根目录 */ public static final String PROJECT_ROOT_DIRECTORY = System.getProperty("user.dir"); /** * word文件名 */ public static final String FILE_NAME_WORD = "test.doc"; /** * excel文件名 */ public static final String FILE_NAME_EXCEL = "test.xls"; }
5、编写测试Controller
@Slf4j @RestController @RequestMapping("/api") public class DemoController { @Value("${pageoffice.posyspath}") private String poSysPath; @Value("${pageoffice.popassword}") private String poPassWord; @Value("${pageoffice.docpath}") private String docPath; /** * 被`@PostConstruct`修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。 */ @PostConstruct public void init() { poSysPath = Constants.PROJECT_ROOT_DIRECTORY + poSysPath; docPath = Constants.PROJECT_ROOT_DIRECTORY + docPath; } @RequestMapping("/hello") public String hello() { log.info("hello ..."); return "HelloWorld~"; } @RequestMapping(value = "/word", method = RequestMethod.GET) public ModelAndView showWord(HttpServletRequest request, Map<String, Object> map) { log.info("编辑word ..."); PageOfficeCtrl poCtrl = new PageOfficeCtrl(request); // 设置服务页面 poCtrl.setServerPage("/poserver.zz"); // 添加自定义保存按钮 poCtrl.addCustomToolButton("保存", "Save", 1); // 添加自定义盖章按钮 poCtrl.addCustomToolButton("盖章", "AddSeal", 2); // 拿到请求前缀做拼接保存文件方法 String requestApiPrefix = request.getServletPath().replace("/word", ""); // 设置处理文件保存的请求方法 poCtrl.setSaveFilePage(requestApiPrefix + "/save"); // 打开word poCtrl.webOpen("file://" + docPath + Constants.FILE_NAME_WORD, OpenModeType.docAdmin, "张三"); map.put("pageoffice", poCtrl.getHtmlCode("PageOfficeCtrl1")); ModelAndView mv = new ModelAndView("Word"); return mv; } @RequestMapping(value = "/excel", method = RequestMethod.GET) public ModelAndView showExcel(HttpServletRequest request, Map<String, Object> map) { log.info("编辑excel ..."); PageOfficeCtrl poCtrl = new PageOfficeCtrl(request); // 设置服务页面 poCtrl.setServerPage("/poserver.zz"); // 添加自定义保存按钮 poCtrl.addCustomToolButton("保存", "Save", 1); // 添加自定义盖章按钮 poCtrl.addCustomToolButton("盖章", "AddSeal", 2); // 拿到请求前缀做拼接保存文件方法 String requestApiPrefix = request.getServletPath().replace("/excel", ""); // 设置处理文件保存的请求方法 poCtrl.setSaveFilePage(requestApiPrefix + "/save"); // 打开word poCtrl.webOpen("file://" + docPath + Constants.FILE_NAME_EXCEL, OpenModeType.xlsNormalEdit, "张三"); map.put("pageoffice", poCtrl.getHtmlCode("PageOfficeCtrl1")); ModelAndView mv = new ModelAndView("Excel"); return mv; } @RequestMapping("/save") public void saveFile(HttpServletRequest request, HttpServletResponse response) { log.info("保存文件 ..."); FileSaver fs = new FileSaver(request, response); fs.saveToFile(docPath + fs.getFileName()); fs.close(); } /** * 添加PageOffice的服务器端授权程序Servlet(必须) * * @return */ @Bean public ServletRegistrationBean servletRegistrationBean() { com.zhuozhengsoft.pageoffice.poserver.Server poserver = new com.zhuozhengsoft.pageoffice.poserver.Server(); // 设置PageOffice注册成功后,license.lic文件存放的目录 poserver.setSysPath(poSysPath); ServletRegistrationBean srb = new ServletRegistrationBean(poserver); srb.addUrlMappings("/poserver.zz"); srb.addUrlMappings("/posetup.exe"); srb.addUrlMappings("/pageoffice.js"); srb.addUrlMappings("/jquery.min.js"); srb.addUrlMappings("/pobstyle.css"); srb.addUrlMappings("/sealsetup.exe"); return srb; } /** * 添加印章管理程序Servlet(可选) */ @Bean public ServletRegistrationBean servletRegistrationBean2() { com.zhuozhengsoft.pageoffice.poserver.AdminSeal adminSeal = new com.zhuozhengsoft.pageoffice.poserver.AdminSeal(); // 设置印章管理员admin的登录密码 adminSeal.setAdminPassword(poPassWord); // 设置印章数据库文件poseal.db存放的目录 adminSeal.setSysPath(poSysPath); ServletRegistrationBean srb = new ServletRegistrationBean(adminSeal); srb.addUrlMappings("/adminseal.zz"); srb.addUrlMappings("/sealimage.zz"); srb.addUrlMappings("/loginseal.zz"); return srb; } }
6、准备测试word和excel文件
注意将测试word和excel文件(
文件不能为0字节
)放在项目根目录/pageoffice/doc
目录下,以及新建lic
文件夹…三、测试
访问http://localhost:8080/,点击
打开Word文档
第一次使用的时候会提示安装PageOffice
,直接下一步安装即可~可参考PageOffice客户端安装步骤:https://www.kancloud.cn/pageoffice_course_group/pageoffice_course/654031
安装完成之后回到页面再次点击打开Word文档
,这时候需要填写注册信息,序列号在之前下载的包里面可以找到
注册成功后,正常打开Word文件,之后就可以进行自己的神操作了…
打开Excel文档
如下:
四、Vue页面集成PageOffice
1、项目的
index.html
中引入pageoffice.js
<!-- 引用后端项目中的pageoffice.js文件 --> <script type="text/javascript" src="http://localhost:8080/pageoffice.js"></script>
2、页面
<template> <div class="app-container"> <el-button><a href="javascript:POBrowser.openWindowModeless('http://localhost:8080/api/word','width=1200px;height=800px;');">打开Word文档</a></el-button> <el-button><a href="javascript:POBrowser.openWindowModeless('http://localhost:8080/api/excel','width=1200px;height=800px;');">打开Excel文档</a></el-button> </div> </template> <style lang="scss" scoped></style>
这里页面很简单就2个按钮…
主要通过openWindowModeless
使用非模态框的形式打开文件用法可参考:http://www.zhuozhengsoft.com/help/js3/pobrowser/function/openwindowmodeless.htm
3、后端解决前后端分离情况下跨域问题
@Configuration public class CorsConfig { private CorsConfiguration config() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowCredentials(true); // ① 设置你要允许的网站域名,如果全允许则设为 * corsConfiguration.addAllowedOrigin("*"); // corsConfiguration.addAllowedOrigin("http://www.zhengqingya.com"); // ② 如果要限制 HEADER 或 METHOD 请自行更改 corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config()); return new CorsFilter(source); } }
五、问题 -> 使用Pageoffice打开Word时报
0x80040154
错误问题出现可能原因:安装WPS时关联了.doc.xls.ppt等文件,导致Office无法自动关联
解决:
- 想办法去掉默认关联
- 直接卸载WPS
- 自行谷歌
本文案例demo源码
-
HarmonyOS学习路之开发篇——Page Ability
2021-06-15 12:39:52Page Ability基本概念、Page Ability生命周期、AbilitySlice间导航、跨设备迁移Page Ability
Page Ability基本概念
Page与AbilitySlice
Page模板(以下简称“Page”)是FA唯一支持的模板,用于提供与用户交互的能力。一个Page可以由一个或多个AbilitySlice构成,AbilitySlice是指应用的单个页面及其控制逻辑的总和。
当一个Page由多个AbilitySlice共同构成时,这些AbilitySlice页面提供的业务能力应具有高度相关性。例如,新闻浏览功能可以通过一个Page来实现,其中包含了两个AbilitySlice:一个AbilitySlice用于展示新闻列表,另一个AbilitySlice用于展示新闻详情。Page和AbilitySlice的关系如图1所示。
图1 Page与AbilitySlice
相比于桌面场景,移动场景下应用之间的交互更为频繁。通常,单个应用专注于某个方面的能力开发,当它需要其他能力辅助时,会调用其他应用提供的能力。例如,外卖应用提供了联系商家的业务功能入口,当用户在使用该功能时,会跳转到通话应用的拨号页面。与此类似,HarmonyOS支持不同Page之间的跳转,并可以指定跳转到目标Page中某个具体的AbilitySlice。
AbilitySlice路由配置
虽然一个Page可以包含多个AbilitySlice,但是Page进入前台时界面默认只展示一个AbilitySlice。默认展示的AbilitySlice是通过setMainRoute()方法来指定的。如果需要更改默认展示的AbilitySlice,可以通过addActionRoute()方法为此AbilitySlice配置一条路由规则。此时,当其他Page实例期望导航到此AbilitySlice时,可以在Intent中指定Action,详见不同Page间导航。
setMainRoute()方法与addActionRoute()方法的使用示例如下:
public class MyAbility extends Ability { @Override public void onStart(Intent intent) { super.onStart(intent); // set the main route setMainRoute(MainSlice.class.getName()); // set the action route addActionRoute("action.pay", PaySlice.class.getName()); addActionRoute("action.scan", ScanSlice.class.getName()); } }
addActionRoute()方法中使用的动作命名,需要在应用配置文件(config.json)中注册:
{ "module": { "abilities": [ { "skills":[ { "actions":[ "action.pay", "action.scan" ] } ] ... } ] ... } ... }
Page Ability生命周期
系统管理或用户操作等行为均会引起Page实例在其生命周期的不同状态之间进行转换。Ability类提供的回调机制能够让Page及时感知外界变化,从而正确地应对状态变化(比如释放资源),这有助于提升应用的性能和稳健性。
Page生命周期回调
Page生命周期的不同状态转换及其对应的回调,如图1所示。
图1 Page生命周期
- onStart()
当系统首次创建Page实例时,触发该回调。对于一个Page实例,该回调在其生命周期过程中仅触发一次,Page在该逻辑后将进入INACTIVE状态。开发者必须重写该方法,并在此配置默认展示的AbilitySlice。
@Override public void onStart(Intent intent) { super.onStart(intent); super.setMainRoute(FooSlice.class.getName()); }
- onActive()
Page会在进入INACTIVE状态后来到前台,然后系统调用此回调。Page在此之后进入ACTIVE状态,该状态是应用与用户交互的状态。Page将保持在此状态,除非某类事件发生导致Page失去焦点,比如用户点击返回键或导航到其他Page。当此类事件发生时,会触发Page回到INACTIVE状态,系统将调用onInactive()回调。此后,Page可能重新回到ACTIVE状态,系统将再次调用onActive()回调。因此,开发者通常需要成对实现onActive()和onInactive(),并在onActive()中获取在onInactive()中被释放的资源。 - onInactive()
当Page失去焦点时,系统将调用此回调,此后Page进入INACTIVE状态。开发者可以在此回调中实现Page失去焦点时应表现的恰当行为。 - onBackground()
如果Page不再对用户可见,系统将调用此回调通知开发者用户进行相应的资源释放,此后Page进入BACKGROUND状态。开发者应该在此回调中释放Page不可见时无用的资源,或在此回调中执行较为耗时的状态保存操作。 - onForeground()
处于BACKGROUND状态的Page仍然驻留在内存中,当重新回到前台时(比如用户重新导航到此Page),系统将先调用onForeground()回调通知开发者,而后Page的生命周期状态回到INACTIVE状态。开发者应当在此回调中重新申请在onBackground()中释放的资源,最后Page的生命周期状态进一步回到ACTIVE状态,系统将通过onActive()回调通知开发者用户。 - onStop()
- 系统将要销毁Page时,将会触发此回调函数,通知用户进行系统资源的释放。销毁Page的可能原因包括以下几个方面:
- 用户通过系统管理能力关闭指定Page,例如使用任务管理器关闭Page。
- 用户行为触发Page的terminateAbility()方法调用,例如使用应用的退出功能。 配置变更导致系统暂时销毁Page并重建。
- 系统出于资源管理目的,自动触发对处于BACKGROUND状态Page的销毁。
AbilitySlice生命周期
AbilitySlice作为Page的组成单元,其生命周期是依托于其所属Page生命周期的。AbilitySlice和Page具有相同的生命周期状态和同名的回调,当Page生命周期发生变化时,它的AbilitySlice也会发生相同的生命周期变化。此外,AbilitySlice还具有独立于Page的生命周期变化,这发生在同一Page中的AbilitySlice之间导航时,此时Page的生命周期状态不会改变。
AbilitySlice生命周期回调与Page的相应回调类似,因此不再赘述。由于AbilitySlice承载具体的页面,开发者必须重写AbilitySlice的onStart()回调,并在此方法中通过setUIContent()方法设置页面,如下所示:
@Override protected void onStart(Intent intent) { super.onStart(intent); setUIContent(ResourceTable.Layout_main_layout); }
AbilitySlice实例创建和管理通常由应用负责,系统仅在特定情况下会创建AbilitySlice实例。例如,通过导航启动某个AbilitySlice时,是由系统负责实例化;但是在同一个Page中不同的AbilitySlice间导航时则由应用负责实例化。
Page与AbilitySlice生命周期关联
当AbilitySlice处于前台且具有焦点时,其生命周期状态随着所属Page的生命周期状态的变化而变化。当一个Page拥有多个AbilitySlice时,例如:MyAbility下有FooAbilitySlice和BarAbilitySlice,当前FooAbilitySlice处于前台并获得焦点,并即将导航到BarAbilitySlice,在此期间的生命周期状态变化顺序为:
FooAbilitySlice从ACTIVE状态变为INACTIVE状态。
BarAbilitySlice则从INITIAL状态首先变为INACTIVE状态,然后变为ACTIVE状态(假定此前BarAbilitySlice未曾启动)。
FooAbilitySlice从INACTIVE状态变为BACKGROUND状态。
对应两个slice的生命周期方法回调顺序为:FooAbilitySlice.onInactive() --> BarAbilitySlice.onStart() --> BarAbilitySlice.onActive() --> FooAbilitySlice.onBackground()
在整个流程中,MyAbility始终处于ACTIVE状态。但是,当Page被系统销毁时,其所有已实例化的AbilitySlice将联动销毁,而不仅是处于前台的AbilitySlice。
AbilitySlice间导航
同一Page内导航
当发起导航的AbilitySlice和导航目标的AbilitySlice处于同一个Page时,您可以通过present()方法实现导航。如下代码片段展示通过点击按钮导航到其他AbilitySlice的方法:
@Override protected void onStart(Intent intent) { ... Button button = ...; button.setClickedListener(listener -> present(new TargetSlice(), new Intent())); ... }
如果开发者希望在用户从导航目标AbilitySlice返回时,能够获得其返回结果,则应当使用presentForResult()实现导航。用户从导航目标AbilitySlice返回时,系统将回调onResult()来接收和处理返回结果,开发者需要重写该方法。返回结果由导航目标AbilitySlice在其生命周期内通过setResult()进行设置。
@Override protected void onStart(Intent intent) { ... Button button = ...; button.setClickedListener(listener -> presentForResult(new TargetSlice(), new Intent(), 0)); ... } @Override protected void onResult(int requestCode, Intent resultIntent) { if (requestCode == 0) { // Process resultIntent here. } }
系统为每个Page维护了一个AbilitySlice实例的栈,每个进入前台的AbilitySlice实例均会入栈。当开发者在调用present()或presentForResult()时指定的AbilitySlice实例已经在栈中存在时,则栈中位于此实例之上的AbilitySlice均会出栈并终止其生命周期。前面的示例代码中,导航时指定的AbilitySlice实例均是新建的,即便重复执行此代码(此时作为导航目标的这些实例是同一个类),也不会导致任何AbilitySlice出栈。
不同Page间导航
AbilitySlice作为Page的内部单元,以Action的形式对外暴露,因此可以通过配置Intent的Action导航到目标AbilitySlice。Page间的导航可以使用startAbility()或startAbilityForResult()方法,获得返回结果的回调为onAbilityResult()。在Ability中调用setResult()可以设置返回结果。详细用法可参考根据Operation的其他属性启动应用中的示例。
跨设备迁移
跨设备迁移(下文简称“迁移”)支持将Page在同一用户的不同设备间迁移,以便支持用户无缝切换的诉求。以Page从设备A迁移到设备B为例,迁移动作主要步骤如下:
- 设备A上的Page请求迁移。
- HarmonyOS处理迁移任务,并回调设备A上Page的保存数据方法,用于保存迁移必须的数据。
- HarmonyOS在设备B上启动同一个Page,并回调其恢复数据方法。 开发者可以参考以下详细步骤开发具有迁移功能的Page。
实现IAbilityContinuation接口
说明
一个应用可能包含多个Page,仅需要在支持迁移的Page中通过以下方法实现IAbilityContinuation接口。同时,此Page所包含的所有AbilitySlice也需要实现此接口。- onStartContinuation()
Page请求迁移后,系统首先回调此方法,开发者可以在此回调中决策当前是否可以执行迁移,比如,弹框让用户确认是否开始迁移。 - onSaveData()
如果onStartContinuation()返回true,则系统回调此方法,开发者在此回调中保存必须传递到另外设备上以便恢复Page状态的数据。 - onRestoreData()
源侧设备上Page完成保存数据后,系统在目标侧设备上回调此方法,开发者在此回调中接受用于恢复Page状态的数据。注意,在目标侧设备上的Page会重新启动其生命周期,无论其启动模式如何配置。且系统回调此方法的时机在onStart()之前。 - onCompleteContinuation()
目标侧设备上恢复数据一旦完成,系统就会在源侧设备上回调Page的此方法,以便通知应用迁移流程已结束。开发者可以在此检查迁移结果是否成功,并在此处理迁移结束的动作,例如,应用可以在迁移完成后终止自身生命周期。 - onRemoteTerminated()
如果开发者使用continueAbilityReversibly()而不是continueAbility(),则此后可以在源侧设备上使用reverseContinueAbility()进行回迁。这种场景下,相当于同一个Page(的两个实例)同时在两个设备上运行,迁移完成后,如果目标侧设备上Page因任何原因终止,则源侧Page通过此回调接收终止通知。
请求迁移
实现IAbilityContinuation的Page可以在其生命周期内,调用continueAbility()或continueAbilityReversibly()请求迁移。两者的区别是,通过后者发起的迁移此后可以进行回迁。
try { continueAbility(); } catch (IllegalStateException e) { // Maybe another continuation in progress. ... }
以Page从设备A迁移到设备B为例,详细的流程如下:
- 设备A上的Page请求迁移。
- 系统回调设备A上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onStartContinuation()方法,以确认当前是否可以立即迁移。
- 如果可以立即迁移,则系统回调设备A上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onSaveData()方法,以便保存迁移后恢复状态必须的数据。
- 如果保存数据成功,则系统在设备B上启动同一个Page,并恢复AbilitySlice栈,然后回调IAbilityContinuation.onRestoreData()方法,传递此前保存的数据;此后设备B上此Page从onStart()开始其生命周期回调。
- 系统回调设备A上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onCompleteContinuation()方法,通知数据恢复成功与否。
请求回迁
使用continueAbilityReversibly()请求迁移并完成后,源侧设备上已迁移的Page可以发起回迁,以便使用户活动重新回到此设备。
try { reverseContinueAbility(); } catch (IllegalStateException e) { // Maybe another continuation in progress. ... }
以Page从设备A迁移到设备B后并请求回迁为例,详细的流程如下:
- 设备A上的Page请求回迁。
- 系统回调设备B上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onStartContinuation()方法,以确认当前是否可以立即迁移。
- 如果可以立即迁移,则系统回调设备B上Page及其AbilitySlice栈中所有AbilitySlice实例的IAbilityContinuation.onSaveData()方法,以便保存回迁后恢复状态必须的数据。
- 如果保存数据成功,则系统在设备A上Page恢复AbilitySlice栈,然后回调IAbilityContinuation.onRestoreData()方法,传递此前保存的数据。
- 如果数据恢复成功,则系统终止设备B上Page的生命周期。
- onStart()
-
Selenium | PageObject 设计模式
2022-01-05 17:39:17使用 UI 自动化测试工具时(包括 selenium,appium 等),如果无统一模式进行规范,随着用例的增多会变得难以维护,而 PageObject 让自动化脚本井井有序,将 page 单独维护并封装细节,可以使 testcase 更稳健,不... -
Linux内核学习笔记(八)Page Cache与Page回写
2018-06-02 23:38:19此外,还要确保在page cache中的数据更改时能够被同步到磁盘上,后者被称为page回写(page writeback)。一个inode对应一个page cache对象,一个page cache对象包含多个物理page。 对磁盘的数据进行缓存从而提高... -
JavaApp自动化测试系列[v1.0.0][Page Factory模式]
2020-09-04 16:15:46【附源码】PageFactory模式的概念和PO类似,或者说是它的一种扩展,通过注解的方式定位元素对象 -
PageInfo实现快速分页查询
2020-08-17 16:33:25这句代码,然后把查询出来的集合放进 PageInfo 中并返回 .public PageInfo getDemandList(DemandQueryCriteria getDemandList,Integer pageNum,Integer pageSize) { PageHelper.startPage(pageNum,pageSize);... -
SpringBoot PageOffice 在线编辑 (完整版、有源码)
2019-05-27 18:16:59文章目录简介实例环境准备生成license.lic文件把jar安装到maven使项目能够使用maven引入pom.xml 配置application.properties配置项目结构、代码介绍项目结构:BeanLinitConf类说明,PageOffice注入OfficeOnlineApi类... -
pageoffice使用笔记
2020-04-16 19:17:21文章目录springboot项目集成...springboot项目集成pageoffice 在项目src目录下新建lib文件夹,将jar包引入。 pom文件配置依赖 <dependency> <groupId>com.zhuozhengsoft</groupId> <... -
用pyecharts的Page类作图,为什么使用page.render("page_name")修改文件名称会报错,默认名称没有问题
2018-11-26 12:12:22page = Page() ''' 中间是一些数据 ''' page.add(line)#通过add将多张line page.render("page_name")#通过render这个函数修改名字 ########################################### 报错... -
PageOffice 安装使用说明
2019-06-15 00:13:351. 访问pageoffice官网,下载pageoffice开发包。拷 贝 Samples4 文 件 夹 到 Tomcat 的 Webapps 目 录 下 , 访 问 : http://localhost:8080/Samples4/index.html 2. 如果新建网站或集成到您现有的网站里: 1). ... -
DPDK使用hugepage原理总结
2019-05-09 16:53:50hugepage原理参考http://blog.chinaunix.net/uid-28541347-id-5783934.html DPDK版本:17.11.2 hugepage的作用: 1. 就是减少页的切换,页表项减少...DPDK应用使用hugepage前,应保证系统已经配置hugepage (配置... -
pyppeteer.errors.NetworkError: Protocol Error (Page.navigate): Session closed. Most likely the page
2020-04-07 10:25:35//关掉浏览器 content = await page.content() cookies = await page.cookies() await page.evaluate(js1) //在网页中执行js代码 await page.keyboard.press 模拟键盘按下某个按键,目前mac上组合键无效为已知bug ... -
Springboot中MyBatisplus使用IPage和Page分页
2020-11-18 10:47:13一、需求:实现Springboot中MyBatisplus使用IPage和Page分页 二、技术:MyBatisplus的IPage和Page 三、实现 1、代码结构 2、代码详情 (1)Controller package com.xkcoding.rbac.security.controller; ... -
PageHelper分页插件及PageInfo介绍及使用
2020-02-21 17:19:26PageInfo介绍及使用 1.MyBatis分页插件-PageHelper的配置与应用 2.参考封装PageInfo类 3.PageInfo属性表 下载PageInfo文档 1.MyBatis分页插件-PageHelper的配置与应用 pom.xml 引入依赖: <!-- ... -
PageHelper.startPage和new PageInfo(list)的一些探索和思考
2019-08-13 22:13:12平常我们使用分页插件的时候,都是很机械的... PageHelper.startPage(1, 10); Example example = new Example(Employee.class); example.createCriteria().andEqualTo("employeeSex", "男"); List<Employee&g... -
pyecharts丨页面布局工具——Page 和 Grid
2020-07-06 17:12:42让多张图表展示在同一页面一、 令图表垂直布局——Page二、 令图表水平布局——GridP.S. 如何让两个饼图平行排列?P.P.S. 如何让两张纯图片平行排列?P.P.P.S 如何让两个词云水平排列? 比如我想达到的效果图是这样... -
pyecharts源码解读(16)图表类包charts之组合图表: 顺序多图Page
2021-06-14 22:11:32当前pyecharts的版本为1.9.0。 概述 pyecharts/charts/composite_charts/包中的四个模块分别定义了组合图表类,其中pyecharts/charts/...Page类的签名为class Page(page_title: str = "Awesome-pyecharts", js_ -
Spring Boot/Spring Cloud 集成Page Office支持word、excel、ppt在线浏览编辑
2019-01-16 11:21:57最近做项目用到word、excel的在线浏览编辑功能,在网上找了很多的工具软件,一开始用的是office-online-server,参考office官网文档,最后领导认为他的样式过于简单,支持功能比较少,所以撤换掉用了page office。... -
分页插件中关于PageInfo
2018-07-19 09:55:19//使用分页插件 //传入查询的页码,... PageHelper.startPage(pn,5); List&amp;lt;Employee&amp;gt; emps = employeeService.getAll(); //使用pageInfo包装查询后的结果,封装了详细查询数据,... -
Mit6.S081-实验3-Page tables
2020-11-11 11:36:22Mit6.S081-实验2-System calls一、Print a page table1,实验准备2,实验要求3,system call调用链路4,trace system call具体实现4,执行效果5,测试效果 一、Print a page table 1,实验准备 1)阅读xv6 book章节3... -
MyBatis-Plus分页查询——Page
2021-01-24 09:54:30@Test void contextLoads() { Page<User> page = new Page(1, 2); Page<User> userPage = userMapper.selectPage(page, null); System.out.println(userPage.getTotal()); userPage.getRecords().forEach(System.... -
a padding to disable MSIE and Chrome friendly error page解决方案
2020-10-23 16:25:03a padding to disable MSIE and Chrome friendly error page错误解决方法 -
使用Page对象进行分页
2019-06-05 13:12:441.本分页功能主要使用到了三个主要对象:Page/PageParam/PageUtils,根据前端输入的参数:查询条件以及PageParam对象. ps:还用到了PageHelper 主要思路是根据查询条件将所需要的全部数据查到,然后根据PageParam中的... -
使用PageInfo做分页时手动设置参数的实现方式
2019-03-04 14:47:18使用PageInfo做分页时手动设置参数的实现方式 转载请标明出处: 原文首发于:http://www.zhangruibin.com 本文出自RebornChang的博客 在做项目开发的时候,pagehelper是我们经常使用的一个分页插件,源码地址:... -
PageHelper使用以及PageInfo中分页对象的转化
2019-11-02 10:53:01在使用Mybatis查询数据库展示到前端的过程中不可避免的要考虑到分页问题,这时就引入了Mybatis的PageHelper插件,这个插件对分页功能进行了强有力的...public PageInfo<Po> pageList(Integer pageNum, Intege... -
pageoffice在vue+springboot前后端分离项目中的应用方法
2019-12-11 20:08:261.因浏览器禁用插件无法使用内嵌在浏览器内部的pageoffice打开方式,需要使用4.0新增的POBrowser方式来在外部打开一个窗口去在线打开office 故下面介绍的是基于4.0在线打开文档的一种方式 2.因pageoffice提供的示例... -
MybatisPlus的selectPage方法
2020-10-16 11:15:04MybatisPlus的selectPage方法参数说明前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片...