OpenHarmony开源鸿蒙学习入门–系统相机应用源码解析(一)
一、源码解析的目的:
为什么要去做源码解析这件事?我个人认为,首先可以提高我们对代码书写的能力,毕竟官方系统级的应用,会比demo的写法更专业,让我们更能学到些写法技巧。其次,分析源码是对我们学习开发技术,提升最快的方法之一。
确立认知后,我们做一件事才能更有能力的驱动。
二、准备环境:
用git拉代码到本地即可,不过不能直接运行,因为是系统级应用,不能自动签名。应用里有个文件夹是signature,目测应该是签名文件,后面系统梳理一下,把运行系统应用打包签名写下博客。今天先略过。
系统应用是ets 和 js代码都有的版本,不过我们只需要关注eTS部分就行。
三、熟悉项目结构:
整个相机应用的应用架构分为四层,分别是Product产品层,Feaure业务层,Common通用共享层,和OpenHarmony底层系统接口支持能力。
Product产品层对四种类型的设备UI和逻辑进行了拆分,分为手机,手表,电脑Tablet。
四、解析相机源码:
首先需要明确的是,系统相机应用是API9的工程创建。
接下来我们先关注Phone里的代码思路进行拆分学习。
Phone文件夹的结构:
从文件夹命名就发现不同了,有的大驼峰命名,有的全部字母小写。
个人猜测是为了突出重点吧,有明白原因的同学可以留言分享下。文件夹的分类很清晰明了,比较复杂的部分就是pages UI了。
其他几个文件夹的代码都很简单,有的甚至只是初始化而已。
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
}