> 文档中心 > 《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)

《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)


OpenHarmony开源鸿蒙学习入门–系统相机应用源码解析(一)

一、源码解析的目的:

为什么要去做源码解析这件事?我个人认为,首先可以提高我们对代码书写的能力,毕竟官方系统级的应用,会比demo的写法更专业,让我们更能学到些写法技巧。其次,分析源码是对我们学习开发技术,提升最快的方法之一。

确立认知后,我们做一件事才能更有能力的驱动。

二、准备环境:

开源鸿蒙系统相机应用源码 Gitee地址

用git拉代码到本地即可,不过不能直接运行,因为是系统级应用,不能自动签名。应用里有个文件夹是signature,目测应该是签名文件,后面系统梳理一下,把运行系统应用打包签名写下博客。今天先略过。
《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)

系统应用是ets 和 js代码都有的版本,不过我们只需要关注eTS部分就行。

三、熟悉项目结构:
《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)
整个相机应用的应用架构分为四层,分别是Product产品层,Feaure业务层,Common通用共享层,和OpenHarmony底层系统接口支持能力。

Product产品层对四种类型的设备UI和逻辑进行了拆分,分为手机,手表,电脑Tablet。

四、解析相机源码:

首先需要明确的是,系统相机应用是API9的工程创建。如果不太了解API8和API9有什么不同,请先看这篇博客 ==》OpenHarmony开源鸿蒙学习入门–API8升级到API9

接下来我们先关注Phone里的代码思路进行拆分学习。

Phone文件夹的结构:
《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)
从文件夹命名就发现不同了,有的大驼峰命名,有的全部字母小写。
个人猜测是为了突出重点吧,有明白原因的同学可以留言分享下。

文件夹的分类很清晰明了,比较复杂的部分就是pages UI了。
其他几个文件夹的代码都很简单,有的甚至只是初始化而已。
《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)

Application-AbilityStage。

AbilityStage是API9是HAP包的运行时类,类似于Android中的Applicaiton。提供在HAP加载的时候,回调通知状态,可以在此进行该HAP的初始化(如资源预加载,线程创建等)能力。

【不过基本都不会在初始化里做太多耗时操作,因为会非常影响体验,相机应用也没有在onCreate中去初始化worker线程,而是使用单例去做即插即用】

可以看到源码中相机的AbillityStage,啥都没干。

AbilityStage中有特定的AbilityStageContext对象,包含了获取AbilityStage对应的ModuleInfo对象、环境变化对象。

Application-AbilityStage.tsimport AbilityStage from '@ohos.application.AbilityStage'export default class MyAbilityStage extends AbilityStage {  onCreate() {    // 当应用创建时调用。    console.info('MyAbilityStage onCreate.')  }}

common-ModeConfig.ts

开发内容很简单的根据Video 或者 Photo两种模式传参不同,会返回不同数值的top,bottom内边距。

common-ModeConfig.tsexport class ModeConfig {  private photoPadding: PaddingData = { top: 48, bottom: 154 }  private videoPadding: PaddingData = { top: 48, bottom: 0 }  public getPaddingConfig(mode: string): PaddingData {    switch (mode) {    case 'PHOTO':      return this.photoPadding    case 'VIDEO':      return this.videoPadding    }  }}export class PaddingData {  top = 48  bottom = 154}

FormAbility-FormAbility.ts

之前介绍过,API9的Stage模型。将Ability分为Ability和ExtensionAbility两大类。
其中ExtensionAbility又被扩展为ServiceExtensionAbility、FormExtensionAbility、DataShareExtensionAbility等等一系列ExtensionAbility,以便满足更多的使用场景。

而FormExtensionAbility 提供了卡片扩展相关接口。可以看到相机源码里,没有创建卡片相关的能力。

FormAbility-FormAbility.tsimport FormExtension from '@ohos.application.FormExtension';import { Log } from '../../../../../../common/src/main/ets/default/Utils/Log'export default class FormAbility extends FormExtension {    private TAG: string = '[FormAbility]'    onCreate(want) { Log.info(`${this.TAG} form onCreate. want ${JSON.stringify(want)}`); return null;    }    onCastToNormal(formId) { Log.info(`${this.TAG} onCastToNormal, formId: ${formId}`);    }    onUpdate(formId) { Log.info(`${this.TAG} onUpdate, formId: ${formId}`);    }    onVisibilityChange(newStatus) { Log.info(`${this.TAG} onVisibilityChange, newStatus: ${JSON.stringify(newStatus)}`);    }    onEvent(formId, message) { Log.info(`${this.TAG} onEventA, formId: ${formId}, msg: ${message}`);    }    onDestroy() { Log.info(`${this.TAG} onDestroy`);    }};

MainAbility-MainAbility.ts

这个类似于Android中Activity,我们会在继承Activity后创建MainActivity一个原理。

相机在初始化时,对context,lunchWant这些对象和配置信息,进行犬奴缓存。globalThis这个东西,你可以理解为一个全局单例对象,你可以把变量,函数都放在这里。用法很简单,globalThis.XX = ‘’ 就是赋值。globalThis.XX 就是取值。

在onCreate中接着创建了一个permissionFlag全局变量,用于记录相机相关的权限 是否授权的状态。

然后在onWindowStageCreate函数中,对窗户属性进行了操作。设置应用全屏,然后对顶部的状态栏,底部的导航栏进行了设置。具体说明可以看下面代码注释。

Want,这个东西很有意思,官方的说法是封装了基本通信组件的能力。目测是分布式相关的东西,可以通过它去启动,互信设备的ability。

MainAbility-MainAbility.tsimport Ability from '@ohos.application.Ability'import window from '@ohos.window'export default class MainAbility extends Ability {  onCreate(want, launchParam) {// want,当前Ability的Want类型信息,包括ability名称、bundle名称等。// launchParam,创建 ability、上次异常退出的原因信息。    // Ability创建时回调,执行初始化业务逻辑操作。    console.info('Camera MainAbility onCreate.') // 全局上下文对象,类似于Android中Activity中的context    globalThis.cameraAbilityContext = this.context // Ability启动时的参数。    globalThis.cameraAbilityWant = this.launchWant // 相机需要的权限是否已经授权,默认false    globalThis.permissionFlag = false  }  onDestroy() {    // Ability生命周期回调,在销毁时回调,执行资源清理等操作。    console.info('Camera MainAbility onDestroy.')  }  async onWindowStageCreate(windowStage) {    // 当WindowStage创建后调用。    console.info('Camera MainAbility onWindowStageCreate.') // 开启WindowStage生命周期变化的监听。此接口仅可在Stage模型下使用。    windowStage.on('windowStageEvent', (event) => {      console.info('Camera MainAbility onWindowStageEvent: ' + JSON.stringify(event))     // 如果窗口是失焦状态。      if (event === window.WindowStageEventType.INACTIVE) {     //  globalThis?,这里的问号作用是防止空异常,因为globalThis可能在此时还没有创建成功。      // 如果globalThis是空,后面的代码都不会继续执行。这种写法在swift,Dart,TypeScript中都很普及了。globalThis?.stopCameraRecording && globalThis.stopCameraRecording()  // 这里的写法很有意思, && 表示两者都要满足条件为true。 // 全局搜索了stopCameraRecording函数的实现,发现是对视频录制停止的一系列处理。 // 而stopCameraRecording并不是变量,而是个方法体。前面也说过了globalThis既可以存变量也可以存方法体。 // 所以这句话的意思是说,当 globalThis不为空,并且stopCameraRecording已赋值,调用stopCameraRecording函数的实现。Get.      }      globalThis.cameraWindowStageEvent = event    })//  getMainWindow()是系统接口,提供获取当前窗口对象。// 一般开源鸿蒙提供的异步接口都有两种形态,一种是无返回值,设置callback参数。一种是有返回值,Promise。// 而.then的写法,就是对Promise进行级联处理。拿到返回值后直接走后面函数逻辑。Get.    windowStage.getMainWindow().then( // 为了让大家看着方便,我调整了格式    (win) => {      try {      // 设置应用窗口全屏,窗口的布局是否为全屏显示状态,需要注意到的是全屏状态下状态栏、导航栏仍然显示。 win.setLayoutFullScreen(true).then( () => {   console.info('Camera setFullScreen finished.')   // 这里就针对导航栏、状态栏的可见模式进行设置。相机应用设置导航栏显示,状态栏不显示。   // setSystemBarEnable 设置状态栏和导航栏是否显示。 // 例如,需全部显示,该参数设置为["status", "navigation"]; // 不设置,则默认不显示。   win.setSystemBarEnable(['navigation']).then(() => {     console.info('Camera setSystemBarEnable finished.') } )     })// 设置窗口内导航栏、状态栏的属性 win.setSystemBarProperties({ // 设置导航栏颜色为黑色有透明度,导航栏操作按钮颜色为#B3B3B3   navigationBarColor: '#00000000', navigationBarContentColor: '#B3B3B3' }).then(() => {   console.info('Camera setSystemBarProperties.') })// 这里应该改写成下面这样//.then((data)=> {//    console.info('Succeeded in setting the system bar properties. Data: ' + //JSON.stringify(data));//}).catch((err)=>{//    console.error('Failed to set the system bar properties. Cause: ' + //JSON.stringify(err));//}); globalThis.cameraWinClass = win      } catch (err) { console.error('Camera setFullScreen err: ' + err)      }    })// 当前模式是拍照还是录像,去启动分布式相机的相同模式。    if (this.launchWant.parameters.uri === 'capture') {      globalThis.cameraFormParam = { action: 'capture', cameraPosition: 'PHOTO', mode: 'PHOTO'      }    } else if (this.launchWant.parameters.uri === 'video') {      globalThis.cameraFormParam = { action: 'video', cameraPosition: 'VIDEO', mode: 'VIDEO'      }    }// 设置当前stage要展示的UI内容。    windowStage.setUIContent(this.context, 'pages/index', null)  }  onWindowStageDestroy() {  // 当WindowStage销毁后调用。    console.info('Camera MainAbility onWindowStageDestroy.')  }  onForeground() {   // Ability生命周期回调,当应用从后台转到前台时触发。    console.info('Camera MainAbility onForeground.')    globalThis?.onForegroundInit && globalThis.onForegroundInit()  }  onBackground() {  // Ability生命周期回调,当应用从前台转到后台时触发。    console.info('Camera MainAbility onBackground.')    globalThis?.releaseCamera && globalThis.releaseCamera()  }  onNewWant(want) {  // 当ability的启动模式设置为单例时回调会被调用。    console.info('Camera MainAbility onNewWant.')    globalThis.cameraNewWant = want  }