袁钰涵

袁钰涵 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 segmentfault.com/u/yuanhan_5f5f19f9dabdc 编辑
编辑

而受苦又是一个坏习惯

个人动态

袁钰涵 赞了文章 · 55 分钟前

通俗易懂的JUC源码剖析-CopyOnWriteArrayList

前言

众所皆知,ArrayList是线程不安全的,它的所有方法都没有加锁,那么有没有线程安全并且性能高的类呢?那就是CopyOnWriteArrayList

实现原理

首先来看它的关键数据结构:

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

可以看到,底层和ArrayList一样,用数组来保存元素,但它多了把独占锁lock,来保证线程安全。
下面直接进入主题,看看它的add()方法如何实现的:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true; } finally {
        lock.unlock();
    }
}

代码逻辑很清晰明了:
第一步,获取数组的排他锁
第二步,获取数组元素和长度n,拷贝一个n+1长度的新数组
第三步,把待添加的元素e放在最后一个位置
第四步,覆盖旧的数组,返回true表示添加成功
第五步,释放锁
简而言之,它的实现思路就跟它的命名一样,CopyOnWrite,“写时复制”,添加元素的时候,先复制数组,添加完成后覆盖掉旧的数组,这些步骤是在加锁的环境完成的,也就是说这个过程中不会有其他线程同时也在写数组,这就保证了写操作的线程安全。

再来看set()方法:

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);
        // 如果新值与旧值不同,则拷贝一个新数组,并在index处设置新值
        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            // 新值与旧值相同,为了保证volatile语义,也覆盖下数组,即使内容相同。
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

再来看remove()方法

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        // 如果删除的是最后一个元素
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            // 分两步复制
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
            numMoved);
            setArray(newElements);
        }
        return oldValue;
   } finally {
        lock.unlock();
   }
}

再来看get()方法:

public E get(int index) {
    return get(getArray(), index);
}
final Object[] getArray() {
    return array;
}
private E get(Object[] a, int index) {
    return (E) a[index];
}

可以看到,它的get方法分为2步,先获取数组,再获取index位置的元素,这2步都是没有加锁的?为什么不需要加锁呢?
上面提到,add()是先拷贝原数组,然后在拷贝的数组上操作的,在setArray()之前对原数组并没有影响,因此读的时候不需要加锁。虽然不需要加锁,但会出现数据弱一致性问题,下图说明

线程A线程B
a = getArray()
remove(a, index)
get(a, index)

image.png

在A线程获取了数组(a=array)后,还没有来得及获取index位置的元素a[index],线程B删除了index位置的元素,并将array引用指向新的数组(array=newArray),但是由于线程A用的是栈区的数组引用a,它引用的还是删除元素前的数组,因此它还是会访问到index这个被删除的元素,因此说会有数据的弱一致性问题,但不会抛ConcurrentModificationException异常。
它的迭代器iterator也是有这种弱一致性的特性,迭代对象是数组的快照,迭代过程中,如果其他线程修改了数组,对迭代器来说是不可见的。
代码如下:

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}
// COW = Copy On Write
static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    // 数组元素的一份快照
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    // 当前迭代的位置-光标
    private int cursor;
    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
   }
    public boolean hasNext() {
        return cursor < snapshot.length;
    }
    public boolean hasPrevious() {
        return cursor > 0;
    }
    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
 }

由此可以看出,CopyOnWriteArrayList适合用在读多写少的场景,性能会比Vector快,因为Vector的所有方法都加了锁,包括读。
最后提下,CopyOnWriteArraySet就是用CopyOnWriteArrayList实现的,所以原理大同小异,有兴趣的同学自己去看下源码吧。

查看原文

赞 2 收藏 1 评论 0

袁钰涵 赞了文章 · 今天 10:34

《Java 面经手册》PDF,全书5章29节,417页11.5万字,完稿&发版!

作者:小傅哥
博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!😄

一、前言

我膨胀了💥 ,在看了大部分以面试讲解的 Java 文章后,发现很多内容的讲解都偏向于翻译抄书说理论的给答案式讲解,最终把知识弄的云里雾里。

就像我问你:

  • HashCode为什么用31作为乘数,你证明过吗?
  • 扰动函数的函数作用是什么,它还有什么场景在用?
  • 拉链寻址和开放寻址具体是什么表现,怎么解决的碰撞问题?
  • ThreadLocal 的实现中还有黄金分割点的使用,你知道吗?
  • CLH、MCS,都是怎么实现的公平锁,代码是什么样?
  • jvmti 可以用于非入侵的监控线程池状态,你用过吗?

关于以上的问题,是不有种即使看过 Java 核心 API 的源码,也很难回答出来?

这是因为 Java 代码本身就是基于数据结构和算法对数学逻辑的具体实现,而那些隐含在代码中的数学知识如果你不会,那么压根你就会忽略掉它,也就因此看不懂源码了。

知识的视觉盲区,就像夜间开车。车灯不够亮你只能看到30%的视野,开的越快越危险!

所以,此书并不是单纯的面试题,也不是内卷八股文。而是从一个单纯的和程序员有关的数学知识点开始,深入讲解 Java 的核心技术。并且每一章节都配有实践验证的源码,可以对照着一起撸才更有感觉!

全书共计5章29节,417页11.5万字,耗时4个月完成。涵盖数据结构、算法逻辑、并发编程、JVM以及简历和互联网大厂面试等内容。

💋鉴于作者水平有限,如果书中含有不易理解的内容,一定是作者在编写的过程中缺少必要的描述和严格的校准,感谢把你的意见或者疑问提交给我,也欢迎与我多一些交互,互相进步共同成长。

二、简介

Hello, world of java ! 你好,java的世界!

欢迎来到这里,很高兴你能拿到这本书。如果你能坚持看完书中每章节的内容,那么不仅可以在你的面试求职上有所帮助,也更能让你对 Java 核心技术有更加深入的学习。

《Java 面经手册》 是一本以面试题为入口讲解 Java 核心技术的 PDF 书籍,书中内容也极力的向你证实代码是对数学逻辑的具体实现为什么这么说? 当你仔细阅读书籍时,会发现这里有很多数学知识,包括:扰动函数、负载因子、拉链寻址、开放寻址、斐波那契(Fibonacci)散列法还有黄金分割点的使用等等。

编码只是在确定了研发设计后的具体实现,而设计的部分包括:数据结构、算法逻辑以及设计模式等,而这部分数据结构和算法逻辑在 Java 的核心 API 中体现的淋漓尽致。那么,也就解释了为什么这些内容成为了热点面试题,虽然可能我们都会觉得这样的面试像是造火箭。

那么,🚕汽车75马力就够奔跑了,那你怎么还想要2.0涡轮+9AT呢?大桥两边的护栏你每次走的时候都会去摸吗?那怎么没有护栏的大桥你不敢上呢?

很多时候,你额外的能力才是自身价值的体现,不要以为你的能力就只是做个业务开发每天CRUD。其实有时候并不是产品让你写CRUD,而是因为你的能力只能产品功能设计成CRUD。

就像数据结构、算法逻辑、源码技能,它都是可以为你的业务开发赋能的,也是写出更好、更易扩展程序的根基,所以学好这份知识非常有必要。

所以,我非常建议你深度阅读此书,如果书中的知识点对你只是抛砖引玉,那么就更好了,你可以继续深入索取,吸纳更多的、更深的内容到自己的头脑中。

1. 适合人群

  1. 具备一定编程基础,工作1-3年的研发人员
  2. 想阅读 Java 核心源码,但总感觉看不懂的
  3. 看了太多理论,但没有实践验证的
  4. 求职面试,总被面试题搞的死去活来的

2. 我能学到什么

  1. 怎么写简历、怎么面大厂、怎么补充不足
  2. Java 核心API中用到的数据结构和算法逻辑
  3. 必会的数学知识,扰动函数、负载因子、拉链寻址、开放寻址、斐波那契(Fibonacci)散列法等
  4. 学到学习的能力,跟着作者的分析和学习方式,增强自己的学习能力

3. 阅读建议

本书虽然是源码分析、理论实践,但并不会让读者感觉枯燥。作者:小傅哥,在每一篇的知识里都写下了实践验证的结果,对于每一章节都有对应的源码实现。小伙伴在阅读的时候可以对照源码实践,并且在源码中还包括了一些必备的素材(10万单词表验证扰动函数)、工具、图标等,来让大家切身的体会到知识乐趣。也让所有认真阅读的读者,看后都能让懂了就是真的懂

4. 书籍截图

三、目录

第 1 章 谈谈面试

  • 第 1 节:面试官都问我啥
  • 第 2 节:认知自己的技术栈盲区
  • 第 3 节:简历该怎么写
  • 第 4 节:大厂都爱聊啥

第 2 章 数据结构和算法

  • 第 1 节:HashCode为什么使用31作为乘数
  • 第 2 节:HashMap 源码分析(上)
  • 第 3 节:HashMap 源码分析(下)
  • 第 4 节:2-3树与红黑树学习(上)
  • 第 5 节:2-3树与红黑树学习(下)
  • 第 6 节:ArrayList 详细分析
  • 第 7 节:LinkedList、ArrayList,插入分析
  • 第 8 节:双端队列、延迟队列、阻塞队列
  • 第 9 节:java.util.Collections、排序、二分、洗牌、旋转算法
  • 第 10 节:StringBuilder 与 String 对比
  • 第 11 节:ThreadLocal 源码分析

第 3 章 码农会锁

  • 第 1 节:volatile
  • 第 2 节:synchronized
  • 第 3 节:ReentrantLock 和 公平锁
  • 第 4 节:AQS原理分析和实践运用
  • 第 5 节:AQS 共享锁,Semaphore、CountDownLatch

第 4 章 多线程

  • 第 1 节:Thread.start() 启动原理
  • 第 2 节:Thread,状态转换、方法使用、原理分析
  • 第 3 节:ThreadPoolExecutor
  • 第 4 节:线程池讲解以及JVMTI监控

第 5 章 JVM 虚拟机

  • 第 1 节:JDK、JRE、JVM
  • 第 2 节:JVM 类加载实践
  • 第 3 节:JVM 内存模型
  • 第 4 节:JVM 故障处理工具
  • 第 5 节:GC 垃圾回收

四、PDF📚下载

版权说明11.5万字417页作者:小傅哥的原创书籍《Java 面经手册》,已上架 CSDN 付费下载平台,享受版权保护。但只设置最低下载价格:1.9元,感谢支持和理解。

让人怪不好意思的,没接过广告的号主,只能这样收回点运营博客的服务器成本。更重要的是保护了版权!!!

1. 可获得内容包括

  1. 《Java 面经手册》PDF 完整版书籍一本
  2. 完整版源码一份,共 27 个案例
  3. 可以加入面经专栏讨论群,添加我的微信:fustack,备注:面经入群

2. 下载方式

  1. 公众号:bugstack虫洞栈,回复:面经手册,即可获得最新的下载链接。更新和补充会更换链接
  2. 添加小傅哥微信(fustack),备注:面经
  3. 直接点击本文结尾的,阅读原文,可以直接进入下载

五、🎉收尾

19年7月 ~ 20年初,是小傅哥做技术号主的迷糊状态,没有粉丝基础没有写作经验没有技术文章沉淀、当然也没有运营技巧,而一年后这样一个似乎是闯进了技术圈的该溜子终于在粉丝的包容、理解和支持上,慢慢成长起来了。也有了一本《重学Java设计模式》全网可统计到的 21 万+ 下载量GitHub 项目多次霸榜 Trending,成为全球热门项目。也积累了属于自己的一窝粉丝,感谢你们

再说说《Java 面经手册》,本书到这里还不是结束,接下来还会继续编写,Spring、SpringBoot、Rpc、Mysql以及中间件相关的面经。同样,面经不只是面经,更是核心技术的学习和深入的了解。所有的内容的输出都是一个目的,让更多的人对知识能做到,让懂了就是真的懂!

祝大家在学习过程中都有自己的收获和能力的提升,提前祝新年快乐,平安吉祥!

查看原文

赞 4 收藏 1 评论 0

袁钰涵 发布了文章 · 1月26日

警惕!一个针对安全研究人员进行攻击的组织出现了

编译:袁钰涵

谷歌威胁分析小组发现了一个攻击活动组织,经过几个月的努力,确认该活动组织是针对在不同的公司和组织中从事漏洞研究和开发的安全研究人员进行攻击。

谷歌认为这个活动的参与者是朝鲜网军,攻击活动成立的本身,在于实施社会工程学的人员精通漏洞分析和研究,在过去一段时间中,他们采取了多种手段来锁定研究人员并发起攻击,后文将会讲述他们用了什么方法来锁定研究人员。

谷歌威胁分析小组希望这篇文章能提醒安全研究界的朋友,如今他们成为了这群朝鲜政府支持派的攻击目标。

如何联系研究人员?

为了与安全研究人员获得联系并骗取他们信任,该组织建立了一个研究博客以及多个 Twitter 账号,借用以上两者与潜在目标进行互动。

他们用这些 Twitter 账号发布指向博客的链接,同时声称博客中有人发现了漏洞并对漏洞以利用,发布了漏洞利用的视频,用账号之间互相转发视频扩大其影响力,还在其他用户的帖子下发布这类内容。

这个组织所拥有的 Twitter 账号

组织建立的博客中包含了已公开披露的漏洞文章和分析,还会包含某位研究人员研究漏洞后对博客的投稿,而这些被网站写出“投稿的研究人员”本人对此却完全不知情,组织如此做可能是为了在安全研究人员中获得更高的信任度。

组织对公开披露的漏洞进行分析的示例

尽管无法验证他们发布的所有漏洞的真实性与工作状态,但在组织声称他们发现了有效漏洞并使之运行时,可以知道他们所说是假的。

2021 年 1 月 14 日,组织控制的账号通过 Twitter 分享了他们的 YouTube 视频,视频声称构造了 CVE-2021-1647(一个最近修复的 Windows Defender 漏洞)的利用程序,让它产生 cmd.exe shell,人们仔细观看视频后确认这是一个虚假漏洞。

YouTube 上也有多条评论表示该视频是虚假伪造视频,根本没有可利用的漏洞。

但组织不顾 You Tube 上的评论,用他们控制的其他 Twitter 帐号转发原帖,并评论“不是假冒视频”。

组织对多个账号的“利用”

如何锁定安全研究员?

这个组织对安全研究人员建立最初的联系后,他们会询问研究人员是否希望在漏洞研究方面进行合作,然后为研究人员提供 Visual Studio 项目。

Visual Studio 项目中包含利用此漏洞的源代码,但是在编译该Visual Studio项目的时候会自动生成存在恶意代码的DLL。

DLL 是一个恶意软件,它会与组织控制的 C2 域进行通信,下图显示了 VS Build Event 的示例。

生成 VS Project 文件时执行的 Visual Studio 命令

除了通过骗取信任获得合作等一系列行动锁定攻击目标,有时该组织会直接在安全研究人员访问其博客后马上攻击。

研究人员通过 Twitter 访问 blog.br0vvnn [.] io 的文章不久后,会发现系统被安装了恶意服务,内存后门将开始连接到攻击者拥有的命令和控制服务器。

受攻击时研究人员的系统正在运行最新版本的 Windows 10 和 Chrome 浏览器版本。目前谷歌方面没有发现是否为 Chrome 的 0day 漏洞。

这个组织已使用多个平台与潜在目标进行联系,这些平台包括了 Twitter,LinkedIn,Telegram,Discord,Keybase 和电子邮件。

谷歌威胁分析小组在下面提供已知的组织帐户和别名,如果已与这些帐户中的任何一个进行了交流,或直接访问了他们的博客,就要小心了。

如果担心自己会成为目标,谷歌威胁分析小组建议使用单独的物理机或虚拟机来进行研究活动。

组织控制的网站和帐户

研究博客

https://blog.br0vvnn[.]io

Twitter 帐户

LinkedIn 帐户

Keybase

https://keybase.io/zhangguo

Telegram

https://t.me/james50d

样本 Hash

攻击者拥有的域名

  • angeldonationblog[.]com
  • codevexillium[.]org
  • investbooking[.]de
  • krakenfolio[.]com
  • opsonew3org[.]sg
  • transferwiser[.]io
  • transplugin[.]io

攻击者通过入侵后控制的域名

  • trophylab[.]com
  • www.colasprint[.]com
  • www.dronerc[.]it
  • www.edujikim[.]com
  • www.fabioluciani[.]com

C2 URLs

  • https[:]//angeldonationblog[.]com/image/upload/upload.php
  • https[:]//codevexillium[.]org/image/download/download.asp
  • https[:]//investbooking[.]de/upload/upload.asp
  • https[:]//transplugin[.]io/upload/upload.asp
  • https[:]//www.dronerc[.]it/forum/uploads/index.php
  • https[:]//www.dronerc[.]it/shop_testbr/Core/upload.php
  • https[:]//www.dronerc[.]it/shop_testbr/upload/upload.php
  • https[:]//www.edujikim[.]com/intro/blue/insert.asp
  • https[:]//www.fabioluciani[.]com/es/include/include.asp
  • http[:]//trophylab[.]com/notice/images/renewal/upload.asp
  • http[:]//www.colasprint[.]com/_vti_log/upload.asp

主机 IOC

Registry Keys

  • HKLMSOFTWAREMicrosoftWindowsCurrentVersionKernelConfig
  • HKLMSOFTWAREMicrosoftWindowsCurrentVersionDriverConfig
  • HKCUSOFTWAREMicrosoftWindowsCurrentVersionRunSSL Update

文件路径

  • C:WindowsSystem32Nwsapagent.sys
  • C:WindowsSystem32helpsvc.sys
  • C:ProgramDataUSOShareduso.bin
  • C:ProgramDataVMwarevmnat-update.bin
  • C:ProgramDataVirtualBoxupdate.bin
查看原文

赞 1 收藏 1 评论 0

袁钰涵 关注了用户 · 1月26日

xiangzhihong @xiangzhihong

著有《React Native移动开发实战》1,2、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》和《Android应用开发实战》

关注 7088

袁钰涵 赞了文章 · 1月26日

Flutter混合开发

混合开发简介

使用Flutter从零开始开发App是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的。因此使用Flutter去统一Android、iOS技术栈,把它作为已有原生App的扩展能力,通过有序推进来提升移动终端的开发效率。
目前,想要在已有的原生App里嵌入一些Flutter页面主要有两种方案。一种是将原生工程作为Flutter工程的子工程,由Flutter进行统一管理,这种模式称为统一管理模式。另一种是将Flutter工程作为原生工程的子模块,维持原有的原生工程管理方式不变,这种模式被称为三端分离模式。

在这里插入图片描述
在Flutter框架出现早期,由于官方提供的混编方式以及资料有限,国内较早使用Flutter进行混合开发的团队大多使用的是统一管理模式。但是,随着业务迭代的深入,统一管理模式的弊端也随之显露,不仅三端(Android、iOS和Flutter)代码耦合严重,相关工具链耗时也随之大幅增长,最终导致开发效率降低。所以,后续使用Flutter进行混合开发的团队大多使用三端代码分离的模式来进行依赖治理,最终实现Flutter工程的轻量级接入。
除了可以轻量级接入外,三端代码分离模式还可以把Flutter模块作为原生工程的子模块,从而快速地接入Flutter模块,降低原生工程的改造成本。在完成对Flutter模块的接入后,Flutter工程可以使用Android Studio进行开发,无需再打开原生工程就可以对Dart代码和原生代码进行开发调试。
使用三端分离模式进行Flutter混合开发的关键是抽离Flutter工程,将不同平台的构建产物依照标准组件化的形式进行管理,即Android使用aar、iOS使用pod。也就是说,Flutter的混编方案其实就是将Flutter模块打包成aar或者pod库,然后在原生工程像引用其他第三方原生组件库那样引入Flutter模块即可。

Flutter模块

默认情况下,新创建的Flutter工程会包含Flutter目录和原生工程的目录。在这种情况下,原生工程会依赖Flutter工程的库和资源,并且无法脱离Flutter工程独立构建和运行。
在混合开发中,原生工程对Flutter的依赖主要分为两部分。一个是Flutter的库和引擎,主要包含Flutter的Framework 库和引擎库;另一个是Flutter模块工程,即Flutter混合开发中的Flutter功能模块,主要包括Flutter工程lib目录下的Dart代码实现。
对于原生工程来说,集成Flutter只需要在同级目录创建一个Flutter模块,然后构建iOS和Android各自的Flutter依赖库即可。接下来,我们只需要在原生项目的同级目录下,执行Flutter提供的构建模块命令创建Flutter模块即可,如下所示。

flutter create -t module flutter_library    

其中,flutter_library为Flutter模块名。执行上面的命令后,会在原生工程的同级目录下生成一个flutter_library模块工程。Flutter模块也是Flutter工程,使用Android Studio打开它,其目录如下图所示。
在这里插入图片描述
可以看到,和普通的Flutter工程相比,Flutter模块工程也内嵌了Android工程和iOS工程,只不过默认情况下,Android工程和iOS工程是隐藏的。因此,对于Flutter模块工程来说,也可以像普通工程一样使用 Android Studio进行开发和调试。
同时,相比普通的Flutter工程,Flutter模块工程的Android工程目录下多了一个Flutter目录,此目录下的build.gradle配置就是我们构建aar时的打包配置。同样,在Flutter模块工程的iOS工程目录下也会找到一个Flutter目录,这也是Flutter模块工程既能像Flutter普通工程一样使用Android Studio进行开发调试,又能打包构建aar或pod的原因。

Android集成Flutter

在原生Android工程中集成Flutter,原生工程对Flutter的依赖主要包括两部分,分别是Flutter库和引擎,以及Flutter工程构建产物。

  • Flutter库和引擎:包含icudtl.dat、libFlutter.so以及一些class文件,最终这些文件都会被封装到Flutter.jar中。
  • Flutter工程产物:包括应用程序数据段 isolate_snapshot_data、应用程序指令段 isolate_snapshot_instr、虚拟机数据段vm_snapshot_data、虚拟机指令段vm_snapshot_instr以及资源文件flutter_assets。

和原生Android工程集成其他插件库的方式一样,在原生Android工程中引入Flutter模块需要先在settings.gradle中添加如下代码。

setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir.parentFile,
  'flutter_library/.android/include_flutter.groovy'))

其中,flutter_library为我们创建的Flutter模块。然后,在原生Android工程的app目录的build.gradle文件中添加如下依赖。

dependencies {
    implementation project(":flutter")
}

然后编译并运行原生Android工程,如果没有任何错误则说明集成Flutter模块成功。需要说明的是,由于Flutter支持的最低版本为16,所以需要将Android项目的minSdkVersion修改为16。
如果出现“程序包android.support.annotation不存在”的错误,需要使用如下的命令来创建Flutter模块,因为最新版本的Android默认使用androidx来管理包。

flutter create --androidx -t module flutter_library

对于Android原生工程,如果还没有升级到androidx,可以在原生Android工程上右键,然后依次选择【Refactor】→【Migrate to Androidx】将Android工程升级到androidx包管理。
在原生Android工程中成功添加Flutter模块依赖后,打开原生Android工程,并在应用的入口MainActivity文件中添加如下代码。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View flutterView = Flutter.createView(this, getLifecycle(), "route1");
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        addContentView(flutterView, layoutParams);
    }
}

通过Flutter提供的createView()方法,可以将Flutter页面构建成Android能够识别的视图,然后将这个视图使用Android提供的addContentView()方法添加到父窗口即可。重新运行原生Android工程,最终效果如下图所示。
在这里插入图片描述
如果原生Android的MainActivity加载的是一个FrameLayout,那么加载只需要将Flutter页面构建成一个Fragment即可,如下所示。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentTransaction ft= getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.fragment_container, Flutter.createFragment("Hello Flutter"));
        ft.commit();
    }
}

除了使用Flutter模块方式集成外,还可以将Flutter模块打包成aar,然后再添加依赖。在flutter_library根目录下执行aar打包构建命令即可抽取Flutter依赖,如下所示。

flutter build apk --debug

此命令的作用是将Flutter库和引擎以及工程产物编译成一个aar包,上面命令编译的aar包是debug版本,如果需要构建release版本,只需要把命令中的debug换成release即可。
打包构建的flutter-debug.aar位于.android/Flutter/build/outputs/aar/目录下,可以把它拷贝到原生Android工程的app/libs目录下,然后在原生Android工程的app目录的打包配置build.gradle中添加对它的依赖,如下所示。

dependencies {
  implementation(name: 'flutter-debug', ext: 'aar')   
}

然后重新编译一下项目,如果没有任何错误提示则说明Flutter模块被成功集成到Android原生工程中。

iOS集成Flutter

原生iOS工程对Flutter的依赖包含Flutter库和引擎,以及Flutter工程编译产物。其中,Flutter 库和引擎指的是Flutter.framework等,Flutter工程编译产物指的是 App.framework等。
在原生iOS工程中集成Flutter需要先配置好CocoaPods,CocoaPods是iOS的类库管理工具,用来管理第三方开源库。在原生iOS工程中执行pod init命令创建一个Podfile文件,然后在Podfile文件中添加Flutter模块依赖,如下所示。

flutter_application_path = '../flutter_ library/
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'iOSDemo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  install_all_flutter_pods(flutter_application_path)

  # Pods for iOSDemo
  … //省略其他脚本
end '

然后,关闭原生iOS工程,并在原生iOS工程的根目录执行pod install命令安装所需的依赖包。安装完成后,使用Xcode打开iOSDemo.xcworkspace原生工程。
默认情况下,Flutter是不支持Bitcode的,Bitcode是一种iOS编译程序的中间代码,在原生iOS工程中集成Flutter需要禁用Bitcode。在Xcode中依次选择【TAGETS】→【Build Setttings】→【Build Options】→【Enable Bitcode】来禁用Bitcode,如下图所示。
在这里插入图片描述
如果使用的是Flutter早期的版本,还需要添加build phase来支持构建Dart代码。依次选择【TAGGETS】→【Build Settings】→【Enable Phases】,然后点击左上角的加号新建一个“New Run Script Phase”,添加如下脚本代码。

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

不过,最新版本的Flutter已经不需要再添加脚本了。重新运行原生iOS工程,如果没有任何错误则说明iOS成功集成Flutter模块。
除了使用Flutter模块方式外,还可以将Flutter模块打包成可以依赖的动态库,然后再使用CocoaPods添加动态库。首先,在flutter_library根目录下执行打包构建命令生成framework动态库,如下所示。

flutter build ios --debug

上面命令是将Flutter工程编译成Flutter.framework和App.framework动态库。如果要生成release版本,只需要把命令中的debug换成release即可。
然后,在原生iOS工程的根目录下创建一个名为FlutterEngine的目录,并把生成的两个framework动态库文件拷贝进去。不过,iOS生成模块化产物要比Android多一个步骤,因为需要把Flutter工程编译生成的库手动封装成一个pod。首先,在flutter_ library该目录下创建FlutterEngine.podspec,然后添加如下脚本代码。

Pod::Spec.new do |s|
  s.name             = 'FlutterEngine'
  s.version          = '0.1.0'
  s.summary          = 'FlutterEngine'
  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC
  s.homepage         = 'https://github.com/xx/FlutterEngine'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'xzh' => '1044817967@qq.com' }
  s.source       = { :git => "", :tag => "#{s.version}" }
  s.ios.deployment_target = '9.0'
  s.ios.vendored_frameworks = 'App.framework', 'Flutter.framework'
end

然后,执行pod lib lint命令即可拉取Flutter模块所需的组件。接下来,在原生iOS工程的Podfile文件添加生成的库即可。

target 'iOSDemo' do
    pod 'FlutterEngine', :path => './'
end

重新执行pod install命令安装依赖库,原生iOS工程集成Flutter模块就完成了。接下来,使用Xcode打开ViewController.m文件,然后添加如下代码。

#import "ViewController.h"
#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [[UIButton alloc]init];
    [button setTitle:@"加载Flutter模块" forState:UIControlStateNormal];
    button.backgroundColor=[UIColor redColor];
    button.frame = CGRectMake(50, 50, 200, 100);
    [button setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
    [button addTarget:self action:@selector(buttonPrint) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

- (void)buttonPrint{
    FlutterViewController * flutterVC = [[FlutterViewController alloc]init];
    [flutterVC setInitialRoute:@"defaultRoute"];
    [self presentViewController:flutterVC animated:true completion:nil];
}

@end

在上面的代码中,我们在原生iOS中创建了一个按钮,点击按钮时就会跳转到Flutter页面,最终效果如下图所示。
在这里插入图片描述
默认情况下,Flutter为提供了两种调用方式,分别是FlutterViewController和FlutterEngine。对于FlutterViewController来说,打开ViewController.m文件,在里面添加一个加载flutter页面的方法并且添加一个按钮看来调用。

Flutter模块调试

众所周知,Flutter的优势之一就是在开发过程中使用热重载功能来实现快速调试。默认情况下,在原生工程中集成Flutter模块后热重载功能是失效的,需要重新运行原生工程才能看到效果。如此一来,Flutter开发的热重载优势就失去了,并且开发效率也随之降低。
那么,能不能在混合项目中开启Flutter的热重载呢?答案是可以的,只需要经过如下步骤即可开启热重载功能。首先,关闭原生应用,此处所说的关闭是指关闭应用的进程,而不是简单的退出应用。在Flutter模块的根目录中输入flutter attach命令,然后再次打开原生应用,就会看到连接成功的提示,如下图所示。

在这里插入图片描述
如果同时连接了多台设备,可以使用flutter attach -d 命令来指定连接的设备。接下来,只需要按r键即可执行热重载,按R键即可执行热重启,按d键即可断开连接。
在Flutter工程中,我们可以直接点击debug按钮来进行代码调试,但在混合项目中,直接点击debug按钮是不起作用的。此时,可以使用Android Studio提供的flutter attach按钮来建立与flutter模块的连接,进行实现对flutter模块的代码调试,如图下图所示。

在这里插入图片描述
上面只是完成了在原生工程中引入Flutter模块,具体开发时还会遇到与Flutter模块的通信问题、路由管理问题,以及打包等。

查看原文

赞 34 收藏 24 评论 1

袁钰涵 赞了文章 · 1月26日

Zookeeper思维导图

zookeeper思维导图

常见相关问题

ZooKeeper是什么?

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,这些请求会同时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。 有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。

节点类型

  • PERSISTENT-持久化目录节点 客户端与zookeeper断开连接后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL-临时目录节点 客户端与zookeeper断开连接后,该节点被删除
  • EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点。客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

集群模式

在 ZooKeeper 集群中将服务器分成 Leader 、Follow 、Observer 三种角色服务器,在集群运行期间这三种服务器所负责的工作各不相同:

  • Leader 角色服务器负责管理集群中其他的服务器,是集群中工作的分配和调度者。
  • Follow 服务器的主要工作是选举出 Leader 服务器,在发生 Leader 服务器选举的时候,系统会从 Follow 服务器之间根据多数投票原则,选举出一个 Follow 服务器作为新的 Leader 服务器。
  • Observer 服务器则主要负责处理来自客户端的获取数据等请求,并不参与 Leader 服务器的选举操作,也不会作为候选者被选举为 Leader 服务器。

Zookeeper工作原理

Zookeeper 的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。

Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。

Zookeeper 下 Server工作状态

每个Server在工作过程中有三种状态:

LOOKING:当前Server不知道leader是谁,正在搜寻

LEADING:当前Server即为选举出来的leader

FOLLOWING:leader已经选举出来,当前Server与之同步

Zookeeper分布式锁(文件系统、通知机制)

有了zookeeper的一致性文件系统,锁的问题变得容易。

锁服务可以分为两类,一个是保持独占,另一个是控制时序。

对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁。 对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

zookeeper是如何选取主leader的?

当leader崩溃或者leader失去大多数的follower,这时zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。

Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。

1、Zookeeper选主流程(basic paxos)

(1)选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;

(2)选举线程首先向所有Server发起一次询问(包括自己);

(3)选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;

(4)收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;

(5)线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数,设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。

通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1. 每个Server启动后都会重复以上流程。

在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。

2、Zookeeper选主流程(basic paxos) fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。

Zookeeper同步流程

选完Leader以后,zk就进入状态同步过程。

1、Leader等待server连接;

2、Follower连接leader,将最大的zxid发送给leader;

3、Leader根据follower的zxid确定同步点;

4、完成同步后通知follower 已经成为uptodate状态;

5、Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

查看原文

赞 3 收藏 2 评论 0

袁钰涵 赞了文章 · 1月25日

Redux在React Hook中的使用及其原理

Hello, 各位勇敢的小伙伴, 大家好, 我是你们的嘴强王者小五, 身体健康, 脑子没病.
 

本人有丰富的脱发技巧, 能让你一跃成为资深大咖.

一看就会一写就废是本人的主旨, 菜到抠脚是本人的特点, 卑微中透着一丝丝刚强, 傻人有傻福是对我最大的安慰.

欢迎来到小五随笔系列Redux在React Hook中的使用及其原理.

浅谈Redux

下面将从what, why, how to 三个方面来说说Redux

第一问 what 什么是Redux

将一个web应用拆分成视图层与数据层, Redux就是保存其数据的一个容器, 其本质就是维护一个存储数据的对象.

  • State : 一个存放数据的容器 (一个对象)
const initState = {
  count: 0,
}
  • Action : 一个 want to do 的过程 (计划要做一个什么样的操作)

    • ActionType是对Action的描述, 也是连接ActionReducer的桥梁
    • 本质上是一个由ActionTypepayload(数据)组成的对象
export const increaseConstant = 'INCREASE' // ActionType

{
   type: increaseConstant,
   payload,
}  // Action
  • Reducer : 一个 to do 的过程 (执行Action计划的操作)
case increaseConstant: // 当 ActionType 为 'INCREASE' 时, 执行count++
  return {
    ...state,
    count: payload + 1
  }

第二问 why 为什么要使用Redux

当你不知道是否需要使用Redux的时候, 那就是不需要使用.

下面一组动图很好的描述了一个应用程序的开发过程, 及何时需要Redux.
图片来源及原文链接

  • 游戏初期阶段, 数据单向传递, 父传子

image

  • 游戏进入中期, 开始有少量非父子组件间需要通讯一些数据

image

  • 游戏进入后期, 开始需要大量的数据通讯

image

  • 此时, 就是Redux的用武之地了, 使用Redux后流程如下

image

第三问 how to 怎么使用Redux

在说使用方法之前, 我们先来从头到尾模拟一个Redux的过程, 只要理解原理, 使用起来那不是小菜一碟.

Let's go, come on baby!

image

下面我们就用一个简单的计数器的例子来模拟实现一个Redux的过程:

创建一个count组件

import React, { useState } from 'react'

const CountItem = (props) => {
  const {
    count,
    increase,
  } = props

  return (
    <>
      {count}
      <button onClick={increase}>Count++</button>
    </>
  )
}

const Count = () => {
  const [count, setCount] = useState(0)

  const increase = () => {
    setCount(count + 1)
  }

  return (
    <CountItem
      count={count}
      increase={increase}
    />
  )
}

export default Count

这样一个简单的count组件就创建好了, 现在, 我们想把对数据的操作单独封装成一个方法名为dispatch, 传递的参数为一个Action 格式: { type: xxx, payload: xxx }

封装一个Dispatch函数

const dispatch = (action) => {
  switch(action.type) {
    case 'INCREASE':
      return action.payload + 1
    default:
      break
  }
}

改写increase方法

const increase = () => {
-  setCount(count + 1)
+  setCount(dispatch({type: 'INCREASE', payload: count}))
}

这时, 我们将action对象也抽离出来, 方便复用, 新建action.js.

action.js => 返回action对象

const increaseCount = (payload) => {
  return {
    type: 'INCREASE',
    payload
  }
}

改写increase方法

const increase = () => {
-  setCount(dispatch({type: 'INCREASE', payload: count}))
+  setCount(dispatch(increaseCount(count)))
}

接下来我们把dispatch中的事件操作抽离到reducer中, 新建reducer.js.

reducer.js => 进行数据操作

const reducer = (state, action) => {
  const { type, payload } = action
  switch(type) {
    case 'INCREASE':
      return {
        ...state,
        count: payload + 1
      }
    default:
      return state
  }
}

改写dispatch函数

const dispatch = (action) => {
  const state = {
    count,
  }
  
  const newState = reducer(state, action)

  return newState
}

改写increase方法

const increase = () => {
-  setCount(dispatch(increaseCount(count)))
+  setCount(dispatch(increaseCount(count)).count)
}

接下来, 我们把set方法也拿到dispatch中, 让所有操作都在dispatch中完成.

继续改造dispatch函数, 增加setter做映射

const dispatch = (action) => {
  const state = {
    count,
  }
  
+  const setter = {
+    count: setCount
+  }
  
  const newState = reducer(state, action)
  
+  for (let key in newState) {
+    setter[key](newState[key])
+  }

-  return newState
}

改写increase方法

const increase = () => {
-  setCount(dispatch(increaseCount(count)).count)
+  dispatch(increaseCount(count))
}

这里我们可以看到, action.type是连接actionreducer的桥梁, 我们可以将actionType定义为常量单独保存.

在action中增加actionType

export const increaseConstant = 'INCREASE'

// 替换 action 和 reducer 中的 'INCREASE' 为 increaseConstant

基于现有场景, 如果我们有另一个功能, 而目前的reducer并不能帮助我们很好的把不同的功能划分开来, 我们改造一下reducer, 改造成一个对象, 用对象的key去区分功能.

改写reducer

const reducer = {
  count(state, action) {
    const { type, payload } = action
    switch(type) {
      case increaseConstant:
        return payload + 1
      default:
        break
    }
  },
}

这时我们要遍历reducer, 找到正确的key, 才能让程序正确执行, 我们新建combineReducers.js来完成这步操作.

combineReducers

const combineReducers = (reducer) => {
  return (state, action) => {
    let ret = {}

    for (let key in reducer) {
      ret[key] = reducer[key](state[key], action)
    }
    
    return {
      ...state,
      ...ret,
    }
  }
}

继续改下dispatch函数, 使其支持当前格式reducer.

改写dispatch

-  const newState = reducer(state, action)
+  const newState = combineReducers(reducer)(state, action)

至此, 一个redux的实现过程就完成了, 接下来, 我们实际用一用redux. 其实, 当完成上述操作的时候, 怎么用就已经说的差不多了.

image

Redux + React 使用

action, reducer, Count组件同上, Count组件需要简单改写下.

新建store.js

import { createStore, combineReducers } from 'redux'
import reducer from './recuder'

const initState = {
  count: 0,
}

const store = createStore(
  combineReducers(reducer),
  initState,
)

export default store

新建app.jsx, 引入store

import { Provider } from 'react-redux'
import store from './store'

const App = () => {
  return (
    <Provider store={store}>
      <Count />
    </Provider>
  )
}

export default App

改写Count组件

import React from 'react'
import { connect } from 'react-redux'
import { increaseCount } from './action'

const CountItem = (props) => {
  const {
    count,
    increase,
  } = props

  return (
    <>
      {count}
      <button onClick={increase}>Count++</button>
    </>
  )
}

const Count = (props) => {
  const {
    count,
    dispatch,
  } = props

  const increase = () => {
    dispatch(increaseCount(count))
  }

  return <CountItem count={count} increase={increase} />
}

export default connect(
  (state) => {
    return state
  },
  (dispatch) => {
    return { dispatch }
  }
)(Count)

接下来, 我们改写成hook的写法

改写Count组件

import React from 'react'
- import { connect } from 'react-redux'
+ import { useSelector, useDispatch } from 'react-redux'
import { increaseCount } from './action'

const CountItem = (props) => {
  const {
    count,
    increase,
  } = props

  return (
    <>
      {count}
      <button onClick={increase}>Count++</button>
    </>
  )
}

const Count = () => {
-  const {
-    count,
-    dispatch,
-  } = props
  
+  const count = useSelector(state => state.count)
+  const dispatch = useDispatch()

  const increase = () => {
    dispatch(increaseCount(count))
  }

  return <CountItem count={count} increase={increase} />
}

- export default connect(
-  (state) => {
-      return state
-  },
-  (dispatch) => {
-    return { dispatch }
-  }
- )(Count)

+ export default Count

至此, 本篇文章就到此结束了, 大家可以写个复杂一点的组件练习下, 比如todoList, 手动滑稽. 逃了逃了, 我要去timi去了, 我的小伙伴还等我带他上白银呢, 一帮混子, 带不动.

image

查看原文

赞 12 收藏 7 评论 7

袁钰涵 发布了文章 · 1月25日

当 AI 可以无痕换脸,一次面部信息的泄露,将会带来无法想象的危机

近日,出现了一例用 AI 技术换脸实施的网络诈骗。

1 月 10 日,小李的大学同学小王通过 QQ 联系小李,并问其借钱,小李谨慎地要求对方进行视频通话,想确认是否是小王再选择借钱,视频接通后,对方的确是小王,虽然视频中小王并没有说话,但小李还是打消了顾虑把钱借给了她。后来联系小王时,发现小王 QQ 被盗,小王对此事完全不知情。

民警判断小李遇到了网络诈骗,诈骗团伙使用 AI 换脸,把小王的脸放到了一个有医院背景的人身上,以此骗取了小李的信任。

小李回忆,与小王视频过程中,小王的脸也是只有轻微晃动,没有太大表情变化,甚至没有说话,这些其实都是疑点,不过当时的她没有太过注意。

面部表情变化不大,脸部轻微晃动,也许在面部信息被窃取时,小王只是进行了眨眼、摇头一系列我们经常会遇到的实名认证操作,甚至没有意识到面部信息被窃取,脸就开始被用以犯罪了。

换脸技术真的这么神奇吗?

早在 2019 年便有一款成熟的换脸软件横空出世,名字非常简单,叫“ZAO”,源自“陌陌”,软件中用户不需要动态视频,只需要上传一张照片便能无痕替换掉各种韩剧、日剧、欧美剧中的男女主脸部,把其替换成自己,人脸合成效果足以以假乱真。

不过此时的以假乱真不够成熟,只能在视频中看不出替换痕迹,仿佛是此人演的电视剧,但是为了保证自然度,无论用户上传的照片长相如何,都是套入了明星的脸型当中,于是出现许多视频“美则美矣,不似本人”。

两年后的今天,根据小李遇到的诈骗案件我们可以发现,诈骗团伙并非是用小王的照片随意套入视频人物脸型,毕竟小李能确认这是同学的脸,大概率是使用小王的动态视频,截取脸部替换到了医院中另一个人身上,由此可见 AI 换脸技术在进一步成熟,以假乱真能力进一步提高,从“不似本人”进入到了“就是本人”的阶段。

人们多少次因使用APP提取面部信息而掉进坑里

image.png

当初“ZAO”这款 APP 出现时,一大批网友为了能套入剧中男女主面部,体验一把与男神、女神“合作演戏”的快乐,纷纷下载 APP,毫无戒心地交出了自己的照片,一度出现了“造视频”的拥堵情况。

但随着 APP 使用范围扩大,许多人注意到了其用户协议内容:

如果您把用户内容中的脸换成您或其他人的脸,您同意或确保肖像权利人同意授予“ZAO”极其关联公司全球范围内完全免费、不可撤销、永久、可转授和可再许可的权利。

仔细一看,这个协议完全就是把用户的脸当作免费资源,日后无论是用以任何用途,用户都不得拒绝,且不说视频平台可能会用以 AI 生成侵害用户名誉的视频,一旦公司出售用户面部信息,这些信息后续流入何处便与公司无关,而用户无法找到侵害肖像权的源泉。

除了使用 APP 外,也有明星莫名被 AI 换脸,套入某个视频中,说出本身没有说过的话,视频流传出来仿佛成为了“远古珍贵视频”“明星人设崩塌证据”,即使明星进行了举报与辟谣,但难以保证多年后这类视频又成为“黑料实锤”。

在中国人“眼见为实”的传统下,脸部信息的重要性更是不言而喻,无论我们是明星还是小透明,在这个 AI 越来越智能化的时代,都应该拥有对面部信息的保护意识。

技术进步的同时,法律也在进步,但在法律之外,我们需要保护好自己

2021 年 1 月 1 日,民法典正式实行后,针对肖像权保护的专门规范: 公民享有肖像权,未经本人同意,不得以营利为目的使用公民的肖像。

《最高人民法院关于贯彻执行〈中华人民共和国民法通则〉若干问题的意见(试行)》(以下简称《民通意见》)第139条载明: 以营利为目的,未经公民同意利用其肖像做广告、商标、装饰橱窗等,应当认定为侵犯公民肖像权的行为。

上述规范同时强调了肖像权侵权认定的两个要素——未经权利人同意以及以营利为目的,导致诉讼中被告常以行为的非营利性质主张不构成侵权。

日常生活中,我们也需要注意保护个人信息,虽然法律的出台让不法商家少了许多空子可钻,但遇到诈骗此类已造成经济损失的行为,即使是把犯罪分子抓获,我们的损失也无法再回来。

除了面部信息外,我们的定位信息、身份证信息、银行卡信息都需要注意保护,现在越来越多的 APP 会获取用户信息加以利用,于是出现普通美颜软件需要定位授权,视频软件需要摄像头可打开的授权,游戏软件需要读取手机照片等信息,还有面对软件要求实名认证时,需要判断实名认证的必要性以及注意其用户协议中是否注明将保护用户面部信息。

即使我们只是普通用户,甚至有网友调侃“我都没有经济,谈何经济损失”,但技术能用以犯罪的用途我们是无法想象的,只有出现案件时才能后知后觉,日常生活中注意个人隐私、保护个人信息,这种做法与熟知法律一样重要。

segmentfault 公众号

查看原文

赞 0 收藏 0 评论 0

袁钰涵 发布了文章 · 1月24日

Elastic更改开源协议抵制“白嫖”AWS回应,浪潮UBML开源,支持RISC-V架构的Android系统来了| 思否技术周刊

​值班编辑:袁钰涵


1、AWS 开源:与社区一起逐步实现真正开源的 Elasticsearch

近日,Elastic 在官网发文称将对 Elasticsearch 和 Kibana 在许可证方面进行了重大的更改,由开源 Apache 2.0 许可证改为采用 Elastic License 和 SSPL(服务器端公共许可证)。

对于 Elastic 的这一决策,AWS 在 AWS 开源博客官方博客发表文章《Stepping up for a truly open source Elasticsearch》 — Elastic 正在破坏开放源代码本身的定义,而 AWS 将加紧创建和维护由开源 Elasticsearch 和 Kibana 获得 Apache 许可 2.0 版(ALv2)许可的分支。


2、浪潮 UBML 低代码建模体系正式开源

2021年1月20日,UBML 项目代码正式对外开放。目前开放的代码有建模标准(UBML-Standard)及 UBML-Models,包括面向后端开发的核心模型BE(Business-Entity)、VO(View-Model)和服务模型中的 EAPI(External-API)。项目团队在开放原子开源基金会的孵化过程中,将继续开放出更多组件以丰富生态的发展,欢迎更多的朋友共同参与。

UBML(Unified-Business-Modeling-Language)是一种基于领域特定语言的、用于快速构件应用软件的低代码开发建模语言,是浪潮 iGIX 企业数字化能力平台的核心低代码建模体系。UBML 是开放原子开源基金会旗下的孵化项目。

UBML 作为低代码开发平台的开发语言,是低代码开发平台的核心基础,包含开发语言无关性的建模标准(UBML-Standard),内置了基于 UBML 标准的全栈业务模型(UBML-Models),并提供了可与模型进行全生命周期交互的开发服务与套件(UBML-SDK)及支撑模型运行的运行时框架(UBML-Runtime)。未来,UBML 将引入更多低代码开发工具(UBML-Designer)等,形成完整的低代码开发平台。

UBML 解决了什么问题?

随着数字化转型成为主流,软件作为数字化转型的业务载体,其需求量发生了井喷式增长。Gartner 预计,2021 年市场对于应用开发的需求将五倍于 IT 公司的产能。为填补这一产量缺口,低代码/零代码技术是目前唯一可行的解决方案,必然会有越来越多企业引入这一技术。

低代码开发是一种软件快速开发方式,可以让开发人员通过少量代码甚至零代码完成业务应用的开发。UBML 作为低代码开发平台的核心基础,致力于解决传统代码开发模式下成本高、门槛高,开发速度、灵活性、敏捷性差的问题。可以提高生产力,实现降本增效,为企业数字化转型提供动能。


3、腾讯多环境配置及抓包调试平台 Nohost 正式开源

腾讯开源公众号发布消息称由腾讯 IMWeb 前端团队打造的一个多环境配置及抓包调试平台正式开源。

据悉 Nohost 覆盖了研发过程中开发联调、测试、产品体验三大阶段,提供跨端代理工具、支持一键切换体验测试环境、请求 mock、实时/历史抓包、代理转发等能力,解决了测试环境管理粗糙、易冲突、联调效率慢、配置复杂等问题,支撑了腾讯在线教育近年来数以万计的需求研发,提升了研发过程中联调体验环节的效率。

Nohost 本身是一个通用化设计、可扩展的方案,在腾讯内部以及业界各大前端团队均有较为广泛的使用。在腾讯内有超过 80 个前端团队直接部署了 Nohost 或者基于 Nohost 能力扩展的应用,覆盖使用用户群体超过 1000 人。业界上也有多个公司(小赢科技、网易游戏、字节跳动、转转二手车等)的前端团队独立部署使用。

更多配置或者更高级的应用,可以去 Nohost git 和Nohost 官网了解:
官网:https://nohost.pro/

4、开源服务器被曝 7 大漏洞!黑客可发起 DNS 缓存中毒攻击并远程执行恶意代码

网络安全研究人员在 Dnsmasq 发现了 7 个漏洞,这是一个流行的开源软件服务器,用于缓存域名系统(DNS)响应。

这 7 个漏洞被以色列研究公司 JSOF 统称为“ DNSpooq”,与之前披露的 DNS 架构的弱点相呼应,使得 Dnsmasq 服务器无法抵御一系列攻击。恶意攻击者可以发起 DNS 缓存中毒攻击并远程执行恶意代码。

研究人员在今天发表的一份报告中指出: “我们发现,Dnsmasq 很容易受到偏离路径攻击者(也就是没有观察到 DNS 转发器与 DNS 服务器之间通信的攻击者)的 DNS 缓存中毒攻击。”

由于这 7 个漏洞,攻击者可以在几秒钟到几分钟的时间内同时攻击多个域名,而且没有任何特殊操作。此外,安全研究人员还发现,Dnsmasq 的许多实例被错误地配置为侦听 WAN 接口,黑客可以直接发起网络攻击。

Dnsmasq 是 DNS 伪装的缩写,是一个轻量级软件,具有 DNS 转发功能,用于本地缓存 DNS 记录,从而减少了上游名称服务器的负载,提高了性能。

JSOF发现,截至 2020 年 9 月,大约有 100 万个 Dnsmasq 漏洞实例,该软件包含在 Android 智能手机中,以及数百万个来自 Cisco、Aruba、Technicolor、Redhat、Siemens、Ubiquiti 和 Comcast 的路由器和其他网络设备。


5、CentOS停止维护后,TencentOS Server 要接过接力棒

12月8号,CentOS官方博客发布一条题目为「CentOS Project shifts focus to CentOS Stream」的消息。内容为 CentOS 以前是作为上游供应商的下游版本存在(在上游供应商之后收到补丁和更新),而现在它将转移到上游版本(包含上游供应商之前测试补丁和更新)。

大致意思是「在未来将从CentOS项目转移重心到CentOS Stream中」。也就是说CentOS 生命周期将被终止,CentOS将在不久后停止维护,不再更新,「免费」的 RHEL 再也没有了。。

CentOS 作为 RHEL 的社区 fork 版本,被称为最稳定的发行版,也是世界上使用量最多的服务器发行版之一。虽然短时间内正在使用 CentOS 系统的服务器没有什么影响,但现在正在使用 CentOS 的用户们也要尽早考虑下服务器替换系统了。

TencentOS Server

TencentOS Serverr ( 又名Tencent Linux 简称Tlinux) 为我们带来一个不错的替代方案,它的用户态基于RHEL,是腾讯针对云场景研发的 Linux 操作系统,提供了专门的功能特性和性能优化,为云服务器实例中的应用程序提供高性能,且安全可靠的运行环境。

Tencent Linux 不仅使用免费,在 CentOS(及发行版)上开发的应用程序也直接在 Tencent Linux 上运行,用户还可持续获得腾讯云的更新维护和技术支持,目前安装量已经超过了500万。

TencentOS Server相比CentOS和Ubuntu等发行版的还有很多优势:

  • 经过腾讯大量内部业务十多年的验证和打磨;
  • 顶级内核专家团队的支持;
  • 包含关键的性能优化和针对云与容器场景的定制特性;
  • 强大的运营支持团队,可获得顶尖的商业支持;
  • 支持多计算架构,且提供企业级的稳定性和支持服务;
内核代码已在 GitHub 开源:
https://github.com/Tencent/Te...

6、阿里平头哥成功将 Android 10 移植到 RISC-V 架构上

近日,阿里巴巴旗下平头哥半导体公司(T-Head)于其官方芯片开放社区正式发文宣布,支持 RISC-V 架构的 Android 系统来了。

ICE EVB是T-Head开发的基于XuanTie C910的高性能SoC板。ICE SoC已集成3个XuanTie C910内核(RISC-V 64)和1个GPU内核;优势是运行速度快、性价比高。

该芯片可提供4K @ 60 HEVC / AVC / JPEG解码能力,以及各种用于控制和数据交换的高速接口和外设;适用于3D图形,视觉AI和多媒体处理。

此端口基于android10-release(platform / manifest 1c222b02bde285fe1272b4440584750154d3882d)。现在T-HEAD开发的所有源代码都包含在资源库中,对此项目感兴趣的任何人都可以按照以下说明重现仿真器环境。

代码已在 GitHub 开源:
https://github.com/T-head-Sem...

7、成功打造一个开源软件的秘诀在于开源社区

健康的开源软件项目不能一蹴而就,应该慢慢培养。而培养的秘诀就在于开源社区的建设,如何吸引开源开发者?如何留住开源开发者?如何规避和解决与开源开发者的冲突?如何保持社区发展?以上这些问题是建设开源社区时不得不面对的。

Joe Brockmeier(红帽)在LinuxCon 北美大会上分享了丰富的实践智慧值得我们学习。

演讲节选:

社区这个词出现的频率不断升高,很多人对它只有一个模糊的概念:「构建社区是一件好事」。

但它到底意味着什么呢?Brockmeier 说:「我与许多不同的公司和项目合作过,他们经常表达出想要一个社区」,当我问道:「你想要什么样的社区?哪类人是你的目标人群?谁对你很重要?社区努力的方向是什么?」他们又往往不能回答。


8、OSS Capital 创始人:为了开放的未来

OSS Capital 的创始人兼首席执行官 Joseph Jacks 已经投身开源事业多年,他曾在一篇文章中总结了自己职业生涯,并表示将在未来几十年继续为开源贡献力量。

以下是 Joseph Jacks 这篇文章的编译节选:

  • 为什么使用 COSS 一词?

因为我不喜欢“开源公司”这个词,这个术语是错误的。开源实际上意味着非常特定的东西:当应用到软件源代码时,开源意味着几乎所有人都可以随时查看、运行、修改和分发代码。从这种意义上说,开源实际上是关于启用无权限的权利,而不是其他任何事情。同时,开源不代表免费。

实际上公司的本质不是开源,因此,把一家公司称为“开源”是是矛盾的,这就像说羽毛是重的一样。一家公司绝不可能在任何时候让它的核心代码被外界看的,否则会让任何人对其进行“修改”或“商业化”。实际上,公司必须在本质上进行区分,以便找到合适的产品市场,聘用合适的人员,找到合适的投资者,有效地确定合适的策略等等。

我认为商业开源(COSS)应该有自己的类别。从根本上来讲,基于 OSS 核心技术的公司在各个层面上都是不同的。因此,我认为世界需要一种新的原则来理解、描述和研究这种公司。我们需要一种新的术语、新的思维方式,来激励更多人去理解为什么这种开放核心的方法在整体上比旧的模式更好。

  • 直接OSS价值获取

在未来的几十年中,我相信将会发生两件事:

1.OSS 将继续推动绝大多数真正的基本数字技术的价值创造,以及越来越多的物理技术(原子的世界)的价值。

2.价值捕获的钟摆将逐渐朝着另一个方向摆动—使世界朝着直接获取开源价值的方向发展,这要归功于一种新的小规模但呈指数级增长的商业类别的兴起:商业开源软件,我称之为“COSS”。


9、微软开发工具产品副总裁 Amanda :2021 软件开发趋势

近日,微软开发人员工具产品副总裁 Amanda Silver 在微软官方博客上更新了主题为“2021 年及以后的软件开发”博客文章。

Amanda Silver 在文中讨论了如何在前所未有的需求中帮助发展和支持开发人才,提高开发人员的包容性和速度,并帮助工程团队通过开源和低代码工具进行扩展等问题,并对微软未来的软件开发计划进行了详细的阐述。

以下为 Amanda Silver 文章的编译节选:

2020 年是颠覆性的一年,企业与员工和客户之间的联系几乎在一夜之间发生了转变。企业开始迅速转向远程办公,在几个月内经历了原本可能需要数年的数字化转型。

对于软件开发团队来说,他们迎来了对新功能的紧急要求,以便与客户和社区进行数字化互动。这种转变在很大程度上得益于成为“digital first responders”的开发人员的支持。开发人员将工作负载转移到云中,并找到了新的方法,可以在任何地方更快地编码、协作和发布软件。

虽然我们看到的许多变化都是软件开发团队一直以来在进行的工作,但它们在疫情爆发期间开始飞速发展。2021 年,是时候反思这些巨大的变化,并考虑在我们过渡到混合工作环境时它们将产生的持久变化。这也是思考这些变化将如何影响软件开发的未来以及我们如何在共同构建更具弹性的未来中发挥作用的机会。

image.png

查看原文

赞 0 收藏 0 评论 0

袁钰涵 赞了文章 · 1月22日

AWS 开源:与社区一起逐步实现真正开源的 Elasticsearch

近日,Elastic 在官网发文称将对 Elasticsearch 和 Kibana 在许可证方面进行了重大的更改,由开源 Apache 2.0 许可证改为采用 Elastic License 和 SSPL(服务器端公共许可证)。

对于 Elastic 的这一决策,AWS 在 AWS 开源博客官方博客发表文章《Stepping up for a truly open source Elasticsearch》 — Elastic 正在破坏开放源代码本身的定义,而 AWS 将加紧创建和维护由开源 Elasticsearch 和 Kibana 获得 Apache 许可 2.0 版(ALv2)许可的分支。

以下为 AWS 开源博客发表的文章全文翻译。


上周,Elastic 宣布他们将改变软件许可策略,将不再以 Apache License 2.0 版本(ALv2)发布 Elasticsearch 和Kibana 的新版本。取而代之的是,新版本的软件将在 Elastic License(限制了软件的使用方式)或 Server Side Public License(有一些限制让很多开源社区无法接受)下提供。这意味着 Elasticsearch 和 Kibana 将不再是开源软件。为了确保这两个软件包的开源版本仍然可用并得到很好的支持,包括在我们自己的产品中,我们今天宣布 AWS 将出面创建并维护一个 ALv2 授权的开源 Elasticsearch 和 Kibana 的分叉。

这对 Elasticsearch 社区的 Open Distro 意味着什么?

我们在 2019 年推出了 Open Distro for Elasticsearch,为客户和开发人员提供功能齐全的 Elasticsearch 发行版,提供ALv2授权软件的所有自由。Open Distro for Elasticsearch 是一个 100% 开源的发行版,它提供了几乎每个 Elasticsearch 用户或开发者都需要的功能,包括支持网络加密和访问控制。在构建 Open Distro 的过程中,我们遵循了 "上游先行 "的推荐开源开发实践。所有对Elasticsearch 的改动都以上游 pull requests 的形式发送(#42066, #42658, #43284, #43839, #53643, #57271, #59563, #61400, #64513),然后我们将 Elastic 提供的 "oss "构建包含在我们的发行版中。这确保了我们与上游开发者和维护者合作,而不是创建一个软件的 "fork"。

选择分叉一个项目并不是一个轻率的决定,但是当一个社区的需求出现分歧时,这可能是一条正确的前进道路--就像这里的情况一样。开源软件的一个重要好处是,当这样的事情发生时,如果开发者有足够的动力,他们已经拥有了所有需要的权利,可以自己接手工作。这里有很多成功的案例,比如 Grafana 就是从 Kibana 3 的分叉中产生的。

当AWS决定提供一个基于开源项目的服务时,我们确保我们有能力并准备好在必要时自己维护它。AWS 带来了多年与这些代码库合作的经验,同时也为 Elasticsearch 和 Apache Lucene(Elasticsearch构建的核心搜索库)做出了上游代码贡献--仅 2020 年就有超过 230 个Lucene 贡献。

我们对 Elasticsearch 和 Kibana 的分叉将基于最新的 ALv2 授权代码库,7.10 版本。我们将在未来几周内发布新的 GitHub 仓库。随着时间的推移,这两个版本将被包含在现有的 Open Distro 发行版中,取代 Elastic 提供的 ALv2 构建。我们将长期参与其中,并将以促进健康和可持续的开源实践的方式开展工作--包括实现与贡献者社区共享项目治理。

这对亚马逊 Elasticsearch 服务客户意味着什么?

您可以放心,无论是 Elastic 的许可证变更,还是我们分叉的决定,都不会对您目前享受的 Amazon Elasticsearch 服务(Amazon ES)产生任何负面影响。今天,我们在 Amazon ES 上提供了 18 个版本的Elasticsearch,这些版本都不会受到许可证变更的影响。

未来,Amazon ES 将由 Elasticsearch 和 Kibana 的新分叉提供支持。我们将继续提供新功能、修复和增强功能。我们致力于提供兼容性,以消除您更新客户端或应用程序代码的需要。就像我们今天所做的那样,我们将为您提供一个无缝的升级路径到新版本的软件。

这一变化不会减缓我们为客户提供的增强速度。如果有的话,一个社区拥有的 Elasticsearch 代码库为我们提供了新的机会,使我们在提高稳定性、可扩展性、弹性和性能方面的进展更快。

这对开源社区意味着什么?

开发者出于许多原因而接受开放源码软件,其中最重要的原因可能是可以自由地在他们希望的地方和方式使用该软件。

自 1998 年 "开源 "一词被提出以来,它就有了特定的含义。Elastic 关于 SSPL 是 "自由开放 "的说法是误导和错误的。他们试图宣称开源的好处,同时又在削去开源本身的定义。他们对 SSPL 的选择掩盖了这一点。SSPL 是一个非开源许可证,它的设计看起来像一个开源许可证,模糊了两者之间的界限。正如 Fedora 社区所说的那样,"[将 SSPL 视为'自由'或'开源'会导致[一个]阴影笼罩在 FOSS 生态系统的所有其他许可证上。"

2018 年 4 月,当 Elastic 将他们的专有授权软件与 ALv2 代码共同混合时,他们在 "We Opened X-Pack "中承诺。"我们没有改变Elasticsearch、Kibana、Beats 和 Logstash 的任何 Apache 2.0 代码的授权--我们永远不会改变。" 上周,在违背了这一承诺之后,Elastic 更新了同一页面,并在脚注中写道:"情况有变"。

Elastic 知道他们做的事情很蹊跷。社区已经告诉他们这一点(例如,见BrasseurQuinnDeVaultJacob)。这也是为什么他们觉得有必要写一个额外的虚张声势的博客(在他们最初的许可证更改博客之上),试图将他们的行为解释为 "AWS 让我们这么做"。大多数人并没有被愚弄。我们没有让他们做任何事情。他们认为,限制他们的许可证将锁定其他人提供托管 Elasticsearch 服务,这将让 Elastic 建立更大的业务。当然 Elastic 有权改变他们的许可证,拥有自己的决定。

同时,我们对我们与 Open Distro for Elasticsearch 一起踏上的长期旅程感到兴奋。我们期待着为 Elasticsearch 和 Kibana 提供一个使用 ALv2 许可证的真正的开源选择,并与社区一起建设和支持这个未来。

image.png

查看原文

赞 4 收藏 0 评论 0

认证与成就

  • 获得 77 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-09-14
个人主页被 15.3k 人浏览