目录

  • 引言
  • 相机集成的基础概念
  • HarmonyOS中的Web开发环境
  • 关于获取相机权限
  • 关于HTML文件选择器线程阻塞问题
  • HarmonyOS特有API
  • 结束语

引言

随着移动设备相机技术的发展,相机集成已成为应用开发的常见需求,而HarmonyOS作为新一代操作系统,提供了丰富的API和框架,支持开发者通过Web技术进行相机集成和图片获取。在鸿蒙开发中,关于相机的使用和获取相册照片是非常常用的功能,本文将详细阐述如何在HarmonyOS应用中通过Web页面集成相机功能,并展示拍摄的图片,那么接下来就来详细分析如何HarmonyOS上使用Web前端技术实现相机的集成和图片的捕获,给大家提供实用的开发指导。

相机集成的基础概念

关于相机集成的基本概念,在探讨具体的实现方法之前,让我们先来了解一些基础概念:

  • 相机权限:应用需要请求用户授权以访问相机。
  • 相机硬件:了解设备相机的硬件规格,如分辨率、焦距等。
  • Web API:学习HTML5中与相机相关的Web API,如navigator.mediaDevices.getUserMedia()。

HarmonyOS中的Web开发环境

接下来再来介绍一下HarmonyOS中的Web开发环境,根据鸿蒙官方文档的介绍,HarmonyOS支持使用Web前端技术开发应用,而且提供了一套完整的开发工具和API,开发者可以使用JavaScript、HTML、CSS等技术构建应用,并利用HarmonyOS提供的API实现原生功能。

关于获取相机权限

在HarmonyOS开发中,和移动端开发时候调用获取相机权限一样,在访问相机的时候需要用户授权,下面就是是请求相机权限的步骤:
1.在应用的配置文件中声明相机权限。
2.在应用运行时,通过API请求相机权限。
还有就是关于相机使用的操作流程,如下所示:

用户触发页面中的“照相”按钮——>应用响应点击事件,启动设备的相机进行拍照——>拍照完成后将图片显示在页面的指定区域
在HarmonyOS的ArkTS中,我们需要创建一个自定义组件,用于加载包含拍照按钮的Web页面,并处理从Web页面传递的相机启动请求,而且我们还需要在HTML页面中添加一个按钮来触发相机拍照功能,并编写JavaScript来处理图片的回显。

关于HTML文件选择器线程阻塞问题

关于解决HTML文件选择器线程阻塞问题的策略,其实就是在处理HTML表单时,用到onShowFileSelector函数来响应用户的“选择文件”操作。当用户点击相关按钮,系统会弹出一个窗口供用户选择图库中的图片进行上传,但是在这个过程中可能会遇到一个问题:如果用户首次打开图库后并未选择任何图片,而是将应用切至后台,那么再次尝试上传图片时可能会无法打开图库。
我觉得这个问题的根源在于,首次触发图库选择时启动了一个线程,该线程在等待用户选择图片;如果用户没有选择图片并将应用切至后台,该线程会停滞在选择图片的步骤,从而阻止后续的图库拉起操作。为了避免这种情况,我们在开发过程中实施以下逻辑优化即可,具体如下所示:

  • 当用户点击“选择文件”按钮并拉起图库时,我们启动一个线程来处理这个操作。
  • 如果用户没有选择任何图片并尝试将应用切至后台,我们应该捕获这个行为。
  • 在用户未选择图片的情况下,我们应该返回一个空值,这样可以让当前线程正常结束其生命周期。
  • 当用户再次尝试上传图片时,系统会启动一个新的线程,从而确保图库可以被成功拉起。
    通过实施这个简单的逻辑调整,我们可以有效地避免线程阻塞问题,提升用户体验。开发者在处理类似场景时,应充分考虑线程管理和异常处理策略,以确保应用的稳定性和易用性。那么接下来分享一个实际使用的示例,由于代码部分较长,这里仅展示部分关键代码,具体代码如下所示:
// PhotoViewerPage.ets  
import { webview } from '@system.ArkWeb';  
import { gallery } from '@system.CoreGallery';  
import { CommonError } from '@system.BasicErrorKit';  

@Entry  
@Component  
struct PhotoViewerPage {  
    controller: webview.WebviewController = new webview.WebviewController();  
    build() {  
        Column() {  
            Web({  
                src: $rawfile('photo_capture.html'),  
                controller: this.controller  
            })  
            .onShowFileSelector((event) => {  
                this.launchCamera((uri: string) => {  
                    event?.result.handleFileList([uri]);  
                });  
                return true;  
            });  
        }  
    }  
  
    launchCamera(callback: (uri: string) => void) {  
        const context = getContext(this) as common.UIAbilityContext;  
        const intent = {  
            action: "ohos.intent.action.imageCapture",  
            parameters: {  
                "callerBundleName": context.abilityInfo.bundleName  
            }  
        };  
  
        function onResult(error: CommonError, data: common.AbilityResult) {  
            if (error) {  
                console.error('Camera launch failed:', error);  
                return;  
            }  
            const uri = data.resultData.uri as string;  
            if (uri && callback) {  
                callback(uri);  
            }  
        }  
  
        context.startAbilityForResult(intent, onResult);  
    }  
}
<!-- photo_capture.html -->  
<!DOCTYPE html>  
<html>  
<head>  
    <meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8">  
    <style>  
        /* CSS样式略 */  
    </style>  
</head>  
<body>  
    <h1>拍照并预览图片</h1>  
    <button id="capture-btn">拍照</button>  
    <p id="photo-preview"></p>  
    <img id="photo-display" style="display:none;">  
</body>  
</html>

需要说明的是,AbilityContext.startAbilityForResult方法的使用与返回机制还需要再次介绍一下,该方法用于启动一个Ability,并通过异步回调(AsyncCallback)处理返回结果,而这一操作仅限于在主线程中执行。
当使用此方法启动一个Ability后,其结果返回机制颇具特色。在正常情况下,被启动的Ability可以通过调用terminateSelfWithResult接口来主动终止,并将结果返回给调用方。但是在某些异常情况下,比如Ability被系统杀死,将会向调用方返回异常信息,此时结果码(resultCode)为-1。还有就是,如果被启动的Ability设置为单实例模式,多个应用尝试通过此方法启动该Ability时,只有最后一个调用方会收到正常返回结果,其他调用方将收到异常信息,结果码同样为-1。

我觉得了解这些机制对于鸿蒙开发者来说至关重要,因为它们直接影响到应用的交互逻辑和错误处理策略。而在实际开发中,合理利用startAbilityForResult方法,可以显著提升应用的稳定性和用户体验。那么下面接着分享另外的示例,具体代码如下所示:

import web_webview from '@ohos.web.webview';
import { common, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

class FileResult {
  result: FileSelectorResult;a
  fileSelector: FileSelectorParam;
  constructor(result: FileSelectorResult, fileSelector: FileSelectorParam) {
    this.result = result;
    this.fileSelector = fileSelector;
  }
}
@Entry
@Component
struct WebCameraPage {
  controller: web_webview.WebviewController = new web_webview.WebviewController();
  build() {
    Column() {
      Web({ src: $rawfile("camera.html"), controller: this.controller })
        .onShowFileSelector((event: FileResult) => {
          this.invokeCamera(((uri: string) => {
            event?.result.handleFileList([uri]);
          }))
          return true;
        })
    }
  }

  invokeCamera(callback: (uri: string) => void) {
    const context = getContext(this) as common.UIAbilityContext;
    const want: Want = {
      action: "ohos.want.action.imageCapture",
      parameters: {
        "callBundleName": context.abilityInfo.bundleName,
      }
    };
    const result: (error: BusinessError, data: common.AbilityResult) => void = (error: BusinessError,
      data: common.AbilityResult) => {
      const resultUri: string = data.want?.parameters?.resourceUri as string;
      if (callback && resultUri) {
        callback(resultUri);
      }
    }
    context.startAbilityForResult(want, result);
  }
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8">
    <style>
        body {
          text-align: center;
          margin: 0;
          padding: 20px;
        }

        #image {
          display: none;
          width: 90%;
          max-width: 500px;
          height: auto;
          border: 1px solid #666;
          border-radius: 10px;
          box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
          margin:0 auto;
        }

        #image_preview {
          display: none;
          margin-top: 10px;
          font-size: 16px;
          color: #666;
        }

      .a-upload {
          text-decoration: none;
          padding: 15px 20px;
          height: 40px;
          line-height: 40px;
          position: relative;
          cursor: pointer;
          color: #666;
          border-radius: 20px;
          overflow: hidden;
          display: inline-block;
          font-size: 14px;
          box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
        }

      .a-upload input {
          position: absolute;
          font-size: 100px;
          right: 0;
          top: 0;
          opacity: 0;
          filter: alpha(opacity=0);
          cursor: pointer;
          border-radius: 25px;
        }
    </style>
</head>

<body>
<h1>获取拍照图片</h1>
<a href="javascript:;" class="a-upload" id="a_upload">
    <label class="input-file-button" for="upload">选择照片</label>
    <input type="file" id="upload" name="upload" accept="image/*" capture="upload" onchange="showPic()"/>
</a>
<p id="image_preview"></p>
<img id="image">

<script>
    function showPic() {
      let event = this.event;
      let tFile = event? event.target.files : [];
      if (tFile === 0) {
        document.getElementById('image_preview').style.display = 'block';
        document.getElementById('image_preview').innerHTML = "未选择照片";
        return;
      }
      document.getElementById('image').setAttribute('src', URL.createObjectURL(tFile[0]));
      document.getElementById('image_preview').style.display = 'block';
      document.getElementById('image').style.display = 'block';
    }
</script>
</body>
</html>

注意:在实现过程中,我们需要确保在Web视图中正确处理跨域和安全问题,同时在启动相机或处理文件时,要考虑用户权限的申请和验证,而且处理相机拍照结果时,也要确保图片URL的有效性和安全性。

HarmonyOS特有API

除了上面介绍的关于Web API,HarmonyOS还提供了一些特有的原生的API,比如设备信息获取、文件系统访问等,可以结合使用以提供更丰富的功能。具体在获取到相机支持的输出流能力后,开始创建拍照流,开发流程如下所示:

img

还有关于处理兼容性和异常,在开发过程中,需要考虑不同设备的兼容性和可能的异常情况:

  • 检测并处理用户拒绝相机权限的情况。
  • 针对不同分辨率和方向的设备进行适配。

结束语

通过本文的介绍,我们可以看到HarmonyOS为Web开发者提供了强大的工具和API,使得相机集成和图片获取变得简单而高效。作为开发者,我们可以利用这些技术构建具有丰富交互功能的应用,为用户提供更加便捷的服务。HarmonyOS生态的不断的不断发展,将有更多的创新技术和工具出现,我们应该不断学习和探索,以适应不断变化的技术环境,从而开发出更好的应用。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐