【每日学点鸿蒙知识】包体积优化、WebView下载PDF等
1、打包体积大如何配置优化包体积问题?
问题场景:
含有so库的程序包打包完之后包体积过大,超出预期。
参考答复:
- 首先查看打包的类型,debug编译打包含有调试信息相对于release包的体积较大.可以通过配置"strip": true来去除so中的debug信息减小so体积。该配置需要配置在hap和hsp模块,release和debug模式下都可以配置 :
"nativeLib": {
"debugSymbol": { // 可通过此配置对cpp编译产物so执行strip,移除so中的调试信息与符号表等
"strip": true, // 执行strip
"exclude": [] //执行strip的过滤正则表达式规则
},
- 当前DevEco Studio默认打包应用时不压缩so库文件,配置so压缩选项后,DevEco Studio会将so库文件以压缩形式打包到包中,从而减小应用包大小。修改应用模块配置文件module.json5中的compressNativeLibs字段,将值配置为true,重新编译、打包应用。
{
"module": {
// ...
"compressNativeLibs": true // 标识libs库以压缩存储方式打包
}
}
2、基于WebDownloadDelegate的PDF下载预览
首先用到web的网页拦截接口onInterceptRequest判断该pdf是否需要下载浏览。判断的标识是该url的响应头response header中有没有Content-Disposition属性,如“attachment; filename=XXXX.pdf”。
try {
if (event) {
const webResponse = new WebResourceResponse();
let url = event.request.getRequestUrl()
let headers = event.request.getRequestHeader()
if(headers['content-disposition']){
AlertDialog.show({
title: '文件下载', cancel: () => {
console.log('取消下载');
},
message: '是否下载', confirm: {
value: '确认', action: () => {
this.setDownloadDelegate()
this.controller.startDownload('https://example.com');
}
},
})
}
}
}
如果需要下载后预览则需要使用startDownload()接口发起一个下载,该下载任务也会通过设置的DownloadDelegate来通知app下载的进度。在此之前,先通过setDownloadDelegate()向Web组件注册一个DownloadDelegate来监听页面触发的下载任务。资源由Web组件来下载,Web组件会通过DownloadDelegate将下载的进度通知给应用。
setDownloadDelegate() {
try {
this.delegate.onBeforeDownload((webDownloadItem: web_webview.WebDownloadItem) => {
console.log('will start a download');
// 传入一个下载路径(沙溪路径),并开始下载。
webDownloadItem.start((getContext(this) as common.UIAbilityContext).filesDir + '/test.pdf');
})
// 下载过程中的回调,通过该回调的参数可以了解下载进度等信息。
this.delegate.onDownloadUpdated((webDownloadItem: web_webview.WebDownloadItem) => {
console.log('download update guid: ' + webDownloadItem.getGuid());
})
// 下载失败的通知。
this.delegate.onDownloadFailed((webDownloadItem: web_webview.WebDownloadItem) => {
console.log('download failed guid: ' + webDownloadItem.getGuid());
})
// 下载完成的通知。
this.delegate.onDownloadFinish(async (webDownloadItem: web_webview.WebDownloadItem) => {
console.log('download finish guid: ' + webDownloadItem.getGuid());
let file = fs.statSync((getContext(this) as common.UIAbilityContext).filesDir + '/test.pdf')
console.log('testTag - size: ' + file.size + 'path:');
})
let fileuri = 'file://’ + (getContext(this) as common.UIAbilityContext).filesDir + ‘/test.pdf'
console.log('testTag - ' + fileuri);
this.controller.setDownloadDelegate(this.delegate);
web_webview.WebDownloadManager.setDownloadDelegate(this.delegate)
this.controller.loadUrl(fileuri)
} catch (error) {
console.log(JSON.stringify(error))
}
}
如果不需要下载,则拿到响应头后通过http.createHttp()创建一个任务,然后再调用request根据URL地址,发起HTTP网络请求,在其异步的回调函数里面设置自定义的响应数据。
let httpRequest = http.createHttp();
let promise = httpRequest.request(url, {
method: http.RequestMethod.GET,
connectTimeout: 60000,
readTimeout: 60000,
header:headers
});
promise.then((data:http.HttpResponse) => {
let result = data?.result as ArrayBuffer
webResponse.setResponseCode(200);
webResponse.setReasonMessage('OK');
webResponse.setResponseData(result);
webResponse.setResponseEncoding('UTF_8')
}).catch((err:Error) => {
console.info('error:' + JSON.stringify(err));
});
3、项目编译报错TypeError: Cannot read properties of undefined (reading ‘newFileToResourceList’)
- hvigor/hvigor-config.json5文件中的logging解开level改成debug,debugging中的stacktrace解开并改成true。从而可以在编译构建报错的时候可以看到具体报错位置,随后排查报错位置是否有问题。
- 如果报错位置在collectResourceInFile,需要点进具体报错信息的文件中,并修改系统文件代码添加一行日志,如下:
collectResourceInFile(e,t) {
if(!this.wholeFileInfo[t]){
console.log('info'+t);
}
this.wholeFileInfo[t].newFileToResourceList.add(e)
}
- 然后再重新编译即可在build中看到打印出来的报错文件位置,然后查看该文件导入导出的路径是否有报黄预警,从而排查路径问题(常见路径中大小写、文件路径缺失等) 。
4、异步是否对主线程有影响?
问题描述
1、setInterval 是异步执行的吗?频繁通过setInterval计时是否会影响主线程以及屏幕刷新率;
2、如何自主判断是异步接口?
3、如何在任务中查看当前是否是在主线程还是在其他线程?
解答
1、setinterval是异步的。是否影响主线程,要看setInterval的callback中做了什么(如果仅是少量数据处理,问题不大,如果是大量数据处理,建议使用worker或taskpool。如果是给页面显示的数据重新赋值,过于频繁的确会影响主线程,毕竟绘制是在主线程处理的)
2、HarmonyOS里的异步并发实现有Promise和async/await,有相关实现的就可以理解为该接口是异步接口,详情可以参考文档:异步并发概述 (Promise和async/await)
3、通过process.tid 获取线程id,然后根据线程id 调用不同的接口可以获取线程相关信息。可以通过此方法判断主次线程:
function isMainThread(): boolean {
return process.pid == process.tid
}
5、如何在HSP模块切换根视图?
如何在HSP模块切换根视图,是否需要在HSP模块拿到程序入口的WindowStage进行切换,具体怎么实现?
在HSP页面中,通过windowStage对象的getMainWindowSync接口获取主窗口,然后使用pushUrl跳转到跟页面,在跟页面通过router.pushNamedRoute切换到HSP页面。具体请参考示例代码:
HspPage页面Index代码:
import { window } from '@kit.ArkUI';
@Entry({ routeName: "HspPage" })
@Component
struct Index {
@State message: string = 'This is Hsp Page';
@State windowStage: window.WindowStage | undefined = AppStorage.get<window.WindowStage>("windowStage")
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
if (this.windowStage) {
this.windowStage.getMainWindowSync()
.getUIContext()
.getRouter()
.pushUrl({
url: "pages/Page"
})
}
})
}
.width('100%')
}
.height('100%')
}
}
根页面page1
import { router } from '@kit.ArkUI';
import("lib1/src/main/ets/pages/Index")
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})
.onClick(() => {
console.log("clicked")
router.pushNamedRoute({
name: "HspPage"
})
})
}
.height('100%')
.width('100%')
}
}
根页面page2
import { router } from '@kit.ArkUI';
import("lib1/src/main/ets/pages/Index")
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})
.onClick(() => {
console.log("clicked")
router.pushNamedRoute({
name: "HspPage"
})
})
}
.height('100%')
.width('100%')
}
}