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

一、源码解析的目的:

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

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

二、准备环境:

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

用git拉代码到本地即可,不过不能直接运行,因为是系统级应用,不能自动签名。应用里有个文件夹是signature,目测应该是签名文件,后面系统梳理一下,把运行系统应用打包签名写下博客。今天先略过。

Harmony开发 preferences工具封装 harmony开源代码_OpenHarmony

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

三、熟悉项目结构:

Harmony开发 preferences工具封装 harmony开源代码_OpenHarmony_02


整个相机应用的应用架构分为四层,分别是Product产品层,Feaure业务层,Common通用共享层,和OpenHarmony底层系统接口支持能力。

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

四、解析相机源码:

首先需要明确的是,系统相机应用是API9的工程创建。

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

Phone文件夹的结构:

Harmony开发 preferences工具封装 harmony开源代码_鸿蒙_03


从文件夹命名就发现不同了,有的大驼峰命名,有的全部字母小写。

个人猜测是为了突出重点吧,有明白原因的同学可以留言分享下。文件夹的分类很清晰明了,比较复杂的部分就是pages UI了。

其他几个文件夹的代码都很简单,有的甚至只是初始化而已。

Harmony开发 preferences工具封装 harmony开源代码_鸿蒙_04

Application-AbilityStage。

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

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

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

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

Application-AbilityStage.ts


import 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.ts


export 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.ts


import 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.ts


import 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<XXX>。
	// 而.then的写法,就是对Promise<Window>进行级联处理。拿到返回值后直接走后面函数逻辑。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
  }