EfonMark

一番码客 : 挖掘你关心的亮点。
http://www.efonmark.com

本文目录:

[TOC]

概述

Electron 是基于 Nodejs (nodejs 是一个基于 Chrome V8 引擎的 JavaScript 运行环
境, 可以让 javascript 运行在后端)的一个跨平台开发框架, 可以跨 Linux/Windows/Mac
OS. 其主要使用 Javascript 作为主要开发语言, 同时通过 html, css 甚至 python/C/C++
作为辅助. 由于 VScode 的带动, 基于 Electron 的应用越来越多, 同时 nodejs 社区也持续
的活跃.

一番码客 : 挖掘你关心的亮点。
http://www.efonmark.com

本文目录:

[TOC]

前言

昨天一番解了很久想解这个electron打包python可执行文件为一体的方法,因为要发布我们的小工具,必须要打包为一个安装包啊,不然对于计算机小白来讲太不友好了。今天一番发挥极客精神,必须要将这个问题解决啊,至少。。。。暂时在某种程度上解决这个问题吧。

看来要学习一样技能,还是要成体系的学习才行,但现在这个技术日新月异的时代,等有完整教程或书籍出来时,你已经落后了,或许这个技术已经成熟了。很多技术问题在互联网上才是最新最全的,所以最好的方式,便是结合互联网上的这些最新最全的资料,在自己学习爬坑的过程中,随时整理、记录出一套自己的学习笔记、知识体系,最终当我们对这们技能掌握到一定程度后,也许我们自己的学习笔记,便是一本最新最全的技术指导书,哈哈。

一番码客 : 挖掘你关心的亮点。
http://www.efonmark.com

本文目录:

[TOC]

前言

一番前面用electron+nodejs+vue+python开发了一个pdf合并工具,现在的情况是:

  • "build": "node .electron-vue/build.js && electron-builder"脚本可以单独打包好这个electron工程为exe可执行程序;
  • "build-python": "pyinstaller ./py/api.py --clean --distpath ./pydist"可以打包好python程序为exe可执行程序。
  • 安装完electron的可执行程序,并将打包好的python可执行程序放到electron的安装目录下,这个程序就可以正常运行了。

但显然想要成为一款合格的可发布的程序,这是绝对不能接受的。于是一番就开始了漫长的怕坑之路。

一番码客 : 挖掘你关心的亮点。
http://www.efonmark.com

本文目录:

[TOC]

前言

昨天我们实现了vue下获取单个文件的绝对路径,并且通过另外一个按钮将所选文件的路径显示出来。

显然这是很不人性化的,想要人性化,需要解决两个问题:

  • 一次可以选择多个文件
  • 文件选择完后立即显示出所有所选文件的绝对路径

一番码客 : 挖掘你关心的亮点。
http://www.efonmark.com

本文目录:

[TOC]

前言

我们在开发electron桌面应用时,因为常常希望对一些本地文件做一些操作,需要获取到这个文件的绝对路径。今天一番在electron-vue下实现了单击按钮选择文件,并获取文件绝对路径然后显示出来的效果。在本来要放弃的时候突然柳暗花明。

一番码客 : 挖掘你关心的亮点。
http://www.efonmark.com

本文目录:

[TOC]

前言

之前尝试用electron做一些项目的时候,因为完全没有前端开发经验,做起来还是有点找不到头绪,思路非常乱,想到什么功能便搜什么,这样导致没有全局观,不找到整体的框架甚至基本语法。显然这样效率非常低下。虽然是以项目为驱动,但基础知识还是非常重要。

开始用一个新工具、新语言做项目之前,还是应该快速掌握它最基础的用法和语法,然后在项目中的实际运用中去深入理解和学习更多高阶和常用知识。

意识到这点后,一番开始着手学习vue.js的一些基础知识。

1 - 线程创建

pthread_create

创建一个线程,函数的声明:

1
2
int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
void* (*start_routine)(void*), void* arg)
  • para:
    • thread_out:创建线程后的标识符。
    • attr: 设置线程属性。传NULL为默认属性。
    • start_routine:线程运行函数的函数指针。
    • arg:运行函数的参数,不使用参数则为NULL。
  • return:
    • 0:创建成功。
    • 非0:创建失败,常见错误返回代码EAGAIN(统限制创建新的线程,例如线程数目过多)和EINVAL(线程属性值非法)。

pthread_t

定义在 pthreadtypes.h

1
typedef unsigned long int pthread_t;

线程的标识符。也就是前面创建线程时候传入的参数。

2 - 线程属性设置

pthread_attr_t

属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。pthread_attr_t结构的定义,定义在pthread.h

1
2
3
4
5
6
7
8
9
typedef struct
{
uint32_t flags;
void * stack_base;
size_t stack_size;
size_t guard_size;
int32_t sched_policy;
int32_t sched_priority;
} pthread_attr_t;

属性设置

  • 初始化:pthread_attr_init
  • 默认属性: 非绑定、非分离、1M堆栈、与父进程同样级别的优先级(一般为0)。
  • 设置:属性值只能通过相关函数(**pthread_attr_set×××**)进行设置,这个函数必须在pthread_create函数之前调用。
  • 属性操作函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*
功能:初始化一个线程对象的属性
返回值:若是成功返回0,否则返回错误的编号
形参:attr指向一个线程属性的指针
说明:Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。pthread_attr_init实现时为属性对象分配了动态内存空间。
*/
int pthread_attr_init(pthread_attr_t *attr);


/*
功能:销毁一个线程属性对象
返回值:若是成功返回0,否则返回错误的编号
形参:attr指向一个线程属性的指针
说明:经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。
*/
int pthread_attr_destroy(pthread_attr_t *attr);

3、获取线程间的CPU亲缘性
int pthread_attr_getaffinity_np(pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
说 明:获取线程的CPU亲缘属性
头文件:#include <pthread.h>
4、设置线程的CPU亲缘性
int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
cpusetsize 指向CPU组的缓冲区大小
cpuset 指向CPU组的指针
说 明:通过指定cupset来设置线程的CPU亲缘性
头文件:#include <pthread.h>
5、获取线程分离状态属性
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
detachstate 保存返回的分离状态属性
说 明:获取线程分离状态属性
头文件:#include <pthread.h>
6、修改线程分离状态属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
detachstat 有两个取值
PTHREAD_CREATE_DETACHED(分离)
PTHREAD_CREATE_JOINABLE(非分离)
说 明:Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。
头文件:#include <pthread.h>
7、获取线程的栈保护区大小
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
guardsize 返回获取的栈保护区大小
说 明:获取线程的栈保护区大小
头文件:#include <pthread.h>
8、设置线程的栈保护区大小
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
guardsize 线程的栈保护区大小
说 明:参数提供了对栈指针溢出的保护。
默认为系统页大小
如果设置为0表示没有保护区。
大于0,则会为每个使用 attr 创建的线程提供大小至少为 guardsize 字节的溢出保护区
头文件:#include <pthread.h>
9、获取线程的作用域
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
scope 返回线程的作用域
说 明:指定了作用域也就指定了线程与谁竞争资源
头文件:#include <pthread.h>
10、设置线程的作用域
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
guardsize 线程的作用域,可以取如下值
PTHREAD_SCOPE_SYSTEM 与系统中所有进程中线程竞争
PTHREAD_SCOPE_PROCESS 与当前进程中的其他线程竞争
说 明:指定了作用域也就指定了线程与谁竞争资源
头文件:#include <pthread.h>
11、获取线程的堆栈信息(栈地址和栈大小)
int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
stackaddr 返回获取的栈地址
stacksize 返回获取的栈大小
说 明:获取线程的堆栈地址和大小
头文件:#include <pthread.h>
12、设置线程堆栈区
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
stackaddr 线程的堆栈地址:应该是可移植的,对齐页边距的
可以用posix_memalign来进行获取
stacksize 线程的堆栈大小:应该是页大小的整数倍
说 明:设置堆栈区,将导致pthread_attr_setguardsize失效。
头文件:#include <pthread.h>
13、获取线程堆栈地址
int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stackaddr);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
stackaddr 返回获取的栈地址
说 明:函数已过时,一般用pthread_attr_getstack来代替
头文件:#include <pthread.h>
14、设置线程堆栈地址
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
stackaddr 设置线程堆栈地址
说 明:函数已过时,一般用pthread_attr_setstack来代替
头文件:#include <pthread.h>
15、获取线程堆栈大小
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
stacksize 返回线程的堆栈大小
说 明:获取线程堆栈大小
头文件:#include <pthread.h>
16、设置线程堆栈大小
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
guardsize 线程的栈保护区大小:应该是页大小的整数倍
说 明:设置线程堆栈大小:
头文件:#include <pthread.h>
17、获取线程的调度策略
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
policy 返回线程的调度策略
说 明:获取线程的调度策略
头文件:#include <pthread.h>
18、设置线程的调度策略
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
policy 线程的调度策略,有如下三种:
SCHED_FIFO 先入先出策略
SCHED_RR 轮转调度,类似于 FIFO,但加上了时间轮片算法
SCHED_OTHER 系统默认策略
说 明:设置线程的调度策略
头文件:policy#include <pthread.h>
19、获取线程的调度参数
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
param 返回获取的调度参数,该结构仅有一个从参数,如下
struct sched_param
{
int sched_priority; /* Scheduling priority */
};
说 明:获取线程的调度参数
头文件:#include <pthread.h>
20、设置线程的调度参数
int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
param 要设置的调度参数
说 明:设置线程的调度参数
头文件:#include <pthread.h>
21、获取线程是否继承调度属性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
inheritsched 返回继承调度属性的设置
说 明:获取线程是否继承调度属性
头文件:#include <pthread.h>
22、设置线程是否继承调度属性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
guardsize 设置线程是否继承调度属性
PTHREAD_INHERIT_SCHED:调度属性将继承于正创建的线程
attr中的设置将被忽略
PTHREAD_EXPLICIT_SCHED 调度属性将被设置为attr中指定的属性值
说 明:
头文件:#include <pthread.h>

线程参数传递

参数传递的是指针。代码中将value的值传入。

1
pthread_create(&id, &attr, run, &value);

然后进行指针变量类型转换就可得到值。

1
int value=*(int *)ptr;

Linux内核的三种调度策略

1,SCHED_OTHER:分时调度策略。

它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。

2,SCHED_FIFO:实时调度策略,先到先服务。一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。

它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。

3,SCHED_RR:实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。

鉴于SCHED_FIFO调度策略的一些缺点,SCHED_RR对SCHED_FIFO做出了一些增强功能。从实质上看,它还是SCHED_FIFO调用策略。它使用最大运行时间来限制当前进程的运行,当运行时间大于等于最大运行时间的时候,当前线程将被切换并放置于相同优先级队列的最后。这样做的好处是其他具有相同级别的线程能在“自私“线程下执行。

系统创建线程时,默认的线程是SCHED_OTHER。所以如果我们要改变线程的调度策略的话,可以通过int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)这个函数实现:

线程优先级

主要涉及sched_parampthread_attr_setschedparampthread_attr_getschedparam等方法。

优先级变量存放在结构sched_param中。用pthread_attr_getschedparampthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。

首先,可以通过以下两个函数来获得线程可以设置的最高和最低优先级,函数中的策略即上述三种策略的宏定义:

1
2
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);

SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR支持优先级的使用,他们分别为1和99,数值越大优先级越高。

设置和获取优先级通过以下两个函数:

1
2
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);

上面的param使用了下面的这个数据结构:

1
2
3
4
struct sched_param
{
int __sched_priority; //所要设定的线程优先级
};

例如:

1
param.sched_priority = 51; //设置优先级

线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。
在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

3 - 线程的数据处理

在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。

线程数据和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。也就是说,我们要在线程中使用全局变量,但是这个全局变量在各个线程中是独立的。

例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。

要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。

相关的函数和结构:

  • *pthread_key_t key * :

    指向一个键值的指针 pthread_key_t的定义为typedef int pthread_key_t;不论哪个线程调用了

  • *destructor_function * :
    这是一个销毁函数,它是可选的,可以为 NULL,为 NULL 时,则系统调用默认的销毁函数进行相关的数据注销。如果不为空,则在线程退出时(调用 pthread_exit() 函数)时将以 key 锁关联的数据作为参数调用它,以释放分配的缓冲区,或是关闭文件流等。

  • *pthread_setspecific/pthread_getspecific * :
    设置和获取线程变量的值。

4 - 线程的同步和互斥:

互斥锁

互斥锁用来保证一段时间内只有一个线程在执行一段代码。一般使用流程:

  • 定义一个锁(pthread_mutex_t)
  • 初始化锁(pthread_mutex_init)
  • 使用pthread_mutex_lock/pthread_mutex_unlock进行锁定和解锁。

注意:加锁、解锁之间不能return或break,否则会死锁。

信号量

信号量是一种特殊的变量,本质上是一个非负的整数计数器,可以被增加或减少,但系统保证对该变量的访问是原子操作(这能控制多个线程操作同一资源时的顺序问题)。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。

这里需要引入新的头文件semaphore.h

  • sem_init 初始化信号量。该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.
  • sem_post ( sem_t *sem ) 该函数用于以原子操作的方式将信号量的值加1。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞。
  • sem_wait( sem_t *sem ) 被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减1,表明公共资源经使用后减少。
  • sem_destroy 该函数用于清理用完的信号量。

条件变量

互斥锁是用来给资源上锁的,而条件变量是用来等待而不是用来上锁的。

条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。

通常条件变量和互斥锁同时使用。

和条件变量使用有关的几个重要函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
初始化与销毁:
条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
- 静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.
- 动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.
*/
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
int pthread_cond_destroy(pthread_cond_t *cond);

/*
等待条件:
等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.
当pthread_cond_wait返回时, 互斥量再次被锁住.
*/
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

/*通知条件:
这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号.
必须注意, 一定要在改变条件状态以后再给线程发送信号.
*/
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞

案例

案例:手动自动调焦失效

案例:刷机或者一键还原后进入开机向导, 机器重启

  • 原因:camera模块局部变量锁(scopeLock)没有初始化
  • YN-supernova:fb6ba6dc85076b5d2406c880e513cf3d7d0662fb

案例:camera多处调用的实现

参考