一、问题汇总
1. Ability跳转白屏问题
原因分析: 在配置文件中未配置相关新创建的ability
解决方案: 添加配置文件如下图:
2. 相机无法预览问题
使用官方示例代码,集成后无法预览相机界面?
关键问题异常函数:
cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId);
问题原因:
由于官方示例代码默认使用的是previewProfilesArray[0],所以获取的宽高和下面组件的宽高不一致,这里建议通过动态获取方式去做,如下位置设置宽高和此函数不一致导致无法预览问题:
XComponent({
id: '',
type: 'surface',
libraryname: '',
controller: this.mXComponentController
})
.onLoad(() => {
// 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置
// 预览流与录像输出流的分辨率的宽高比要保持一致
this.mXComponentController.setXComponentSurfaceSize({
// surfaceWidth: displayWidth,
// surfaceHeight: displayHeight
surfaceWidth: 1920, // 此处宽高要和异常函数一致
surfaceHeight: 1080
});
// 获取Surface ID
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
})
.width('100%')
.height('100%')
解决方案:
①在page界面初始化函数中,优先获取previewProfilesArray(具体方法参考官方文档):
// 获取相机设备支持的输出流能力
let cameraOutputCap: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraArray[0]);
if (!cameraOutputCap) {
console.error("cameraManager.getSupportedOutputCapability error");
return;
}
console.info(TAG + "outputCapability: " + JSON.stringify(cameraOutputCap));
let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles;
if (!previewProfilesArray) {
console.error("createOutput previewProfilesArray == null || undefined");
}
②获取完成后使用你想适配的分辨率去设置XComponent中的surfaceWidth和surfaceHeight(这里的page初始方法onPageShow()和aboutToAppear()优先级是高于XComponent组件的加载的)。
比如我使用previewProfilesArray[26]中的width和height也就是1920*1080,如下图:
cameraManager.createPreviewOutput(previewProfilesArray[26], surfaceId);
③ 设置对应surface宽高:
XComponent({
id: '',
type: 'surface',
libraryname: '',
controller: this.mXComponentController
})
.onLoad(() => {
// 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置
// 预览流与录像输出流的分辨率的宽高比要保持一致
this.mXComponentController.setXComponentSurfaceSize({
// surfaceWidth: displayWidth,
// surfaceHeight: displayHeight
surfaceWidth: 1920, // 此处宽高要和异常函数一致
surfaceHeight: 1080
});
// 获取Surface ID
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
})
.width('100%')
.height('100%')
这时候我们使用1920*1080的分辨率就可以成功预览,如果想使用其他分辨率也可以用同样的方式,前提是必须在previewProfilesArray里面支持的范围内去选择。
3.进入相机界面偶发无法预览问题
解决了问题2中的预览问题,后续发现会出现偶发的无法预览问题,经过定位是获取的surfaceId没有及时获取到,就对相机预览进行了配置,导致了相机配置到空的surfaceId导致初始化失败。如下图:
可以看出成功预览是有id的,而偶发的失败情况是没有id的,所以分析到和page的生命周期和函数的使用有关系.
关键问题代码:
可以看到我是在pageshow这个方法中使用了异步处理的方式进行了权限申请和校验,也计时说预览和拍照的方法配置是不同步的。
下面的截图是surface预览ui的id获取,可以看到XComponent中的onLoad函数也是通过回调异步方式。
以上两个关键点就分析出来了,同样的异步操作,会导致偶发性先申请权限成功配置了相机,而XComponent中获取surfaceId的函数还没有成功回调,这样就导致我们使用了空的id去配置了相机。问题原因找到了,下面是优化后的解决方法:
onPageShow() {
console.debug(TAG + "onPageShow")
// 获取屏幕宽高
getDisplay();
// TODO 这里我们删除了申请权限和相机配置
}
然后我们重新在onLoad方法中添加:
.onLoad(() => {
// 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置
// 预览流与录像输出流的分辨率的宽高比要保持一致
this.mXComponentController.setXComponentSurfaceSize({
// surfaceWidth: displayWidth,
// surfaceHeight: displayHeight
surfaceWidth: 1920,
surfaceHeight: 1080
});
// 获取Surface ID
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
// 权限校验
grantPermission().then(res => {
console.info(TAG + TAG, `权限申请成功 ${JSON.stringify(res)}`);
if (res) {
//预览和拍照
cameraShootingCase(getContext(this), this.surfaceId);
}
})
})
到此解决了偶发性的预览问题。
4.闪光灯设置问题FLASH_MODE_OPEN无效
.onClick(() => {
console.info(TAG + 'light onClick')
try {
if (this.flashsrc) {
this.flashsrc = false;
// 关闭闪光灯
if (captureSession != undefined) {
captureSession.setFlashMode(camera.FlashMode.FLASH_MODE_CLOSE);
}
} else {
this.flashsrc = true;
// 开启闪光灯
if (captureSession != undefined) {
//FLASH_MODE_OPEN无法开启闪光灯
captureSession.setFlashMode(camera.FlashMode.FLASH_MODE_OPEN);
}
}
} catch (error) {
let err = error as BusinessError;
console.error(TAG + 'Failed to set the flash mode. errorCode = ' + err.code);
}
//this.context.eventHub.emit('onclick',1);
})
我们将FLASH_MODE_OPEN改为FLASH_MODE_ALWAYS_OPEN即可
captureSession.setFlashMode(camera.FlashMode.FLASH_MODE_ALWAYS_OPEN);
具体原因暂时不清楚可能是官方bug.
5. 使用Flex与组件配合使用宽度不正常问题
已解决:
① 添加 justifyContent: FlexAlign.SpaceEvenly , alignItems: ItemAlign.Center
// 正反面配置
Flex({ direction: FlexDirection.Row , justifyContent: FlexAlign.SpaceEvenly , alignItems: ItemAlign.Center}) {
MySelectButton({ buttonText: $r('app.string.idcard_font') })
.onClick(() => {
});
MySelectButton({ buttonText: $r('app.string.idcard_side') })
.onClick(() => {
});
}.width('100%')
② 删除自定义组件的宽度
// 切换按钮组件
@Component
struct MySelectButton {
private buttonText: Resource = $r('app.string.idcard_font');
@State isSelect: boolean = false; // 选中状态
setSelect(select: boolean) {
this.isSelect = select;
}
build() {
Button({ type: ButtonType.Normal, stateEffect: true }) {
Text(this.buttonText)
.fontColor(this.isSelect == true ? $r('app.color.theme_color') : $r('app.color.galy_color'))
.fontSize(17)
}
// .width('50%') ******************删除此宽度解决
.height(60)
.borderRadius(8) // 圆角
.backgroundColor(Color.White)
// .border({ width: 1 })
// .borderColor($r('app.color.galy_color')) // 描边
.padding(15)
.margin(8)
.shadow({ radius: 10, color: Color.Gray }) // 阴影
}
}
6. 使用imageArrival 视频流回调自动终止问题
如下代码使用: nextImage.release(); 进行释放即可
/**
* 视频流帧数据回调
*/
function onImageArrival(receiver: image.ImageReceiver): void {
receiver.on('imageArrival', () => {
receiver.readNextImage((err: BusinessError, nextImage: image.Image) => {
if (err || nextImage === undefined) {
console.warn(TAG + "onImageArrival readNextImage err:" + err.code)
return;
}
console.warn(TAG + "readNextImage")
nextImage.getComponent(image.ComponentType.JPEG, (err: BusinessError, imgComponent: image.Component) => {
console.warn(TAG + "getComponent")
if (err || imgComponent === undefined) {
console.warn(TAG + "onImageArrival getComponent err:" + err.code)
return;
}
let buffer: ArrayBuffer;
if (imgComponent.byteBuffer as ArrayBuffer) {
buffer = imgComponent.byteBuffer;
console.warn(TAG + "onImageArrival")
nextImage.release(); // 必须进行释放否则无法持续获取帧数据
} else {
console.warn(TAG + "onImageArrival return")
nextImage.release();
return;
}
// do something...;
})
})
})
}
7. 如何将raw下的文件保存至沙盒?
*注意保存txt文档和其他类型或者加密类型文件有略微区别,具体需要看下官方的实现方式
这里举例使用了加密的.mnn文件进行演示
/**
* 初始化文件(流)
*/
public static async initFilesStream(): Promise<void> {
context.resourceManager.getRawFd("etcard_obj_moblie.mnn", async (error, value) => {
if (error != null) {
console.error(`callback getRawFd failed error code: ${error.code}, message: ${error.message}.`);
} else {
let fd = value.fd;
let offset = value.offset;
let length = value.length;
let filesDir = context.filesDir;
// 打开文件流
let inputStream = fs.fdopenStreamSync(fd, "r+");
// let inputStream = fs.createStreamSync(filesDir + '/test.txt', 'r+');
let outputStream = fs.createStreamSync(filesDir + '/etcard_obj_moblie.mnn', "w+");
// 以流的形式读取源文件内容并写入目的文件
let bufSize = 1024;
let readSize = 0;
let buf = new ArrayBuffer(bufSize);
class Option {
public offset: number = 0;
public length: number = bufSize;
}
let option = new Option();
option.offset = value.offset;
if (bufSize > value.length) {
option.length = value.length;
}
let readLen = await inputStream.read(buf, option);
readSize += readLen;
while (readLen > 0) {
await outputStream.write(buf);
option.offset = readSize + value.offset;
option.length = value.length - readSize >= bufSize ? bufSize : value.length - readSize;
readLen = await inputStream.read(buf, option);
readSize += readLen;
}
// 关闭文件流
inputStream.closeSync();
outputStream.closeSync();
}
});
}
8. napi中使用OH_LOG_ERROR等函数打印失效
引入的log.h头文件与LOG_TAG宏定义必须定义后才可以打印效果。