开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

MySQL性能优化(六)-- in和exists

发表于 2019-06-28

in和exists哪个性能更优

sql脚本:

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
复制代码/*建库*/
create database testdb6;
use testdb6;
/* 用户表 */
drop table if exists users;
create table users(
id int primary key auto_increment,
name varchar(20)
);
insert into users(name) values ('A');
insert into users(name) values ('B');
insert into users(name) values ('C');
insert into users(name) values ('D');
insert into users(name) values ('E');
insert into users(name) values ('F');
insert into users(name) values ('G');
insert into users(name) values ('H');
insert into users(name) values ('I');
insert into users(name) values ('J');

/* 订单表 */
drop table if exists orders;
create table orders(
id int primary key auto_increment,/*订单id*/
order_no varchar(20) not null,/*订单编号*/
title varchar(20) not null,/*订单标题*/
goods_num int not null,/*订单数量*/
money decimal(7,4) not null,/*订单金额*/
user_id int not null /*订单所属用户id*/
)engine=myisam default charset=utf8 ;

delimiter ?
drop procedure batch_orders ?

/* 存储过程 */
create procedure batch_orders(in max int)
begin
declare start int default 0;
declare i int default 0;
set autocommit = 0;
while i < max do
set i = i + 1;
insert into orders(order_no,title,goods_num,money,user_id)
values (concat('NCS-',floor(1 + rand()*1000000000000 )),concat('订单title-',i),i%50,(100.0000+(i%50)),i%10);
&emsp; end while;
commit;
end ?
delimiter ;

/*插入1000万条订单数据*/
call batch_orders(10000000); /*插入数据的过程根据机器的性能 花费的时间不同,有的可能3分钟,有的可能10分钟*/

上面的sql中 订单表中(orders) 存在user_id,而又有用户表(users),所以我们用orders表中user_id和user表中的id 来in 和 exists。

结果

1.where后面是小表

(1)select count(1) from orders o where o.user_id in(select u.id from users u);

img

(2)select count(1) from orders o where exists (select 1 from users u where u.id = o.user_id);

img

2.where后面是大表

(1)select count(1) from users u where u.id in (select o.user_id from orders o);

img

(2)select count(1) from users u where exists (select 1 from orders o where o.user_id = u.id);

img

分析

我们用下面的这两条语句分析:

1
2
复制代码select count(1) from orders o where o.user_id in(select u.id from users u);
select count(1) from orders o where exists (select 1 from users u where u.id = o.user_id);

1.in:先查询in后面的users表,然后再去orders中过滤,也就是先执行子查询,结果出来后,再遍历主查询,遍历主查询是根据user_id和id相等查询的。

即查询users表相当于外层循环,主查询就是外层循环

小结:in先执行子查询,也就是in()所包含的语句。子查询查询出数据以后,将前面的查询分为n次普通查询(n表示在子查询中返回的数据行数)

2.exists:主查询是内层循环,先查询出orders,查询orders就是外层循环,然后会判断是不是存在order_id和 users表中的id相等,相等才保留数据,查询users表就是内层循环

这里所说的外层循环和内层循环就是我们所说的嵌套循环,而嵌套循环应该遵循“外小内大”的原则,这就好比你复制很多个小文件和复制几个大文件的区别

小结:如果子查询查到数据,就返回布尔值true;如果没有,就返回布尔值false。返回布尔值true则将该条数据保存下来,否则就舍弃掉。也就是说exists查询,是查询出一条数据就执行一次子查询

结论

小表驱动大表。

in适合于外表大而内表小的情况,exists适合于外表小而内表大的情况。

欢迎关注我的公众号,第一时间接收最新文章~ 搜索公众号: 码咖 或者 扫描下方二维码:
img

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式系统设计模式

发表于 2019-06-27

概述

这篇文章是对于【分布式系统设计模式】的个人理解和部分翻译。

文章探讨了关于《基于容器化软件组件的微服务架构》。

其实容器化编程的发展路径和面向对象编程有异曲同工之妙–
都是将复杂的系统进行抽象、解耦,然后通过一定的方式组合起来。

既然我们要组合,肯定会有面对不同情况的不同组合方式。所以,这些不同的组合方式也会有几个常用的固定模式。
而这个正式我们要探讨的–分布式系统设计模式。

说到分布式,第一个联想到的应该就的容器化。
为什么?其实容器化和分布式本没有交集,只是因为我们发现容器化是一个实现分布式的高效的方法。

容器化设置了一个天然的边界,边界之外用接口进行通信。
有了这个边界的好处就是,任何意料之外的情况都可以被限制在最小的影响范围,毕竟我们构建的是一个大型的复杂系统。

我认为,用FMEA模型能很好的描述为什么会采用容器化去解构分布式系统。(FMEA,可以理解为:失控的状态一定会发生,我们要做的是控制失控的范围)

所以,我们接下来要说的设计模式基本上都是和容器相关,我们需要把容器作为一等公民去看。
毕竟这是写 Kubernetes 的哥们写的。

单容器管理者模式 (Single-container management patterns)

我们为容器增加一些可控接口,比如 run(), stop(), pause(),使得容器对外来说是可控的。

也正是因为广泛的 http 协议支持,你完全可以通过 http 和 JSON这样的序列化方式去构造你应用的对外的 API。

一般来说我们的设计方针都是一个容器提供一种服务。同时容器会为其上下游提供接口。

什么接口?

向上,提供容器本身丰富的信息接口。能够为特定的监控容器运行状态的应用提供信息。

向下,提供了控制容器本身的接口。使得分布式系统能够更有效的管理整个应用的生命周期,以及应用的优先级。

比如,一个集群的管理系统,能够设置集群处理任务的优先级。(比如 K8s 中的抢占式调度)

所以一旦采用这种模式的话,你会需要一个统一的管理平台,通过接口去管理(组管理)单个容器。

单节点-多容器应用模式 (Single-node, multi-container application patterns)

这种模式比较好理解,有些容器应用是需要“共生”的,所以我们会将其放在同一个节点上。
一旦这样,分布式系统会对容器组做联合调度。
比如 K8s 里将调度单位描述成了 Pods(一个 Pod 可能包含多个容器),Nomad 则称其为 task groups。
下面几个就是常用的单节点多容器的设计模式:

副载模式(Sidecar pattern)

多容器部署最通用的一种模式,就是 sidecar 模式。
其实大家都直接称呼 Sidecar 模式,不会翻译成副载。

那 Sidecar 是个啥样子呢?

举个例子吧:我们有一个主容器是 Web Server,我们需要收集 Web Server 所产生的日志。
所以我们会有一个专门的 sidecar 容器,负责收集日志并把日志存储到集群的存储系统。
图片描述

另外一个例子,就是主容器的内容呈现,是由一个 sidecar 容器去实时同步的。

还有个例子是为一个 Http Web Server 提供 Https 功能。
图片描述

你会发现 Sidecar 是主容器的一种扩展和升级,这种模式的好处在于,因为是容器隔离,所以能够保证从属容器不会在主容器需要资源的时候占用过多的资源,因为分配资源的最小单位就是容器。同时,sidecar 一般在功能上比较专职,又是容器化的,所以可以很方便的进行单独的部署、升级。

大使模式(Ambassador pattern)

图片描述

大使模式实现方式是在节点中增加一个通讯代理。他解决的问题是:
为某些年久失修的外部服务,增加一个调用代理,调用者是我们节点上的应用。

大使模式给开发者的好处是:

  1. 他们只要考虑应用与本地服务的连接
  2. 他们可以在本地进行测试
  3. 调用的外部服务语言无关

其实有个很典型但是非容器化的例子,就是 Ribbon 中的客户端负载。
和大使模式很像,所有的请求流量都先经由客户端的负载均衡器决定了流量流向–我们在大使容器中现决定流量流向,然后直接调用真正的服务。

聪明的你会发现,我们似乎可以在大使容器中做很多手脚,比如熔断,路由,流量监控,安全控制等等(有点像服务端的 API 网关)。

没错,所以我们得出了一些使用该模式的场景(来自docs.microsoft.com/en…):

  • 客户端连接语言无关,框架无关
  • 将客户端连接的问题与应用分离,解耦开发
  • 为年久失修的应用程序提供云或集群支持

适配器模式(Adapter pattern)

和 Ambassador 相比,Adapter 模式向外部呈现了一个统一的接口。(方向反了一下)

最典型的例子应该就是容器管理平台,所有系统中的容器都会有一套统一的监控接口。
图片描述

现在的容器多种多样,不过只要保证每个容器都有统一的不变的对外监控接口,对于单独的监控工具来说就不难实时收集各个容器的数据了。

单节点多容器的模式主要就是上面三种。

多节点应用模式 (Multi-node application patterns)

和单节点多应用模式一样,我们同样要求实现这个模式的系统支持 K8s 中 Pod 这样的概念。

领导人选举 (Leader election pattern)

在分布式系统中,老生常谈的问题了,就是领导人选举。

一般领导人选择是怎样一种状态呢?就是在分布式系统中,存在一个或者多个领导人,还有剩下的作为工作节点。
一旦有领导人节点挂了,工作节点按照一定算法升级成为领导人节点。

领导人选举算法是十分复杂的算法(至少对我来说),甚至有些算法库还是语言限定的,所以,更好的方式就是通过容器去使用这个领导人选举功能。

那为什么要分为领导节点和工作节点呢?

当然是为了更好分配任务,分配好了任务,就能保证系统处在一个高效的状态下运行。避免出现有些工作节点空闲,有些工作节点忙的现象。整个模式的思想还是中心化治理最高效的方针。

工作队列模式 (Work queue pattern)

我觉得,分布式系统中的工作队列模式,其实表达了一个具有拓扑关系的分布式系统。每个步骤都是单独的,但是他们的输入可能需要依靠上一个输出,亦或者,他们的输出会成为下一个的输入。

这个模式好在他语言无关,我只要知道我的容器的输入输出,然后将它放在合适的位置就可以了。

同时还需要一个专门进行作业管理的容器进行任务的分发。
图片描述

我在网上并没有找到过多的关于描述 Work queue pattern 的资料。
似乎这种设计模式的叫法不太一样。

向量化模式 (Scatter/gather pattern)

这是一种有点广播竞争的模式。

一个外部请求会先被发送到一个“根”或“父”节点。
然后根节点扩散请求并让服务端同时进行竞争,然后每个竞争者都会返回一部分请求的结果,根节点再把碎片结果揉成一个完整的返回。

这样的模式其实对于开发者的要求还是比较高的,因为要对分布式任务处理中产生的异常有很好的把控,同时涉及到了分布式任务一致性。

一般实现这种模式需要至少 2 个容器,一个是用来进行任务分发的,还有一个是进行结果归总的。

总结

面向对象编程需要面向对象的设计模式作为支撑。
容器化的分布式系统也一样,需要容器的设计模式作为支撑。

上面我们分析了三种容器架构下的设计模式,一共 7 种。
上面这 7 中模式会以组合的形式出现在分布式系统之中,毕竟分布式系统是一个复杂系统。

这篇文章所依据的论文,虽说是分布式系统的设计模式,但是其实会涉及到很多经典的企业集成模式。所以我在这里向大家推荐一本书《企业集成模式》。
可别以为是讲企业管理的,这是给开发者看的。

好吧,暂时先这样吧。

参考

  • segmentfault.com/a/11…
  • www.usenix.org/system…
  • zhuanlan.zhihu.com/p/…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

在Java当中如何优雅地处理临时文件

发表于 2019-06-27

背景

最近接手了同事的代码,发现他是这样处理excel文件的:

1
2
3
复制代码1. 将文件保存到一个事先定义好的目录;如果目录不存在则新建
2. 使用excel处理工具根据文件路径读取文件内容之后处理业务逻辑
3. 编写一个定时任务每天凌晨1点删除该目录内的.xlsx文件

这样虽然可以达到效果,但实在是繁琐至极,非常不优雅。其实jdk中就提供了处理临时文件(Temporary File)的方法,现在让我们来看一看。

创建临时文件

在java中创建临时文件有许多场景,但是大多数是在单元测试或者是对上传的文件进行内容处理。当测试用例或者文件处理完成后,你并不关心文件是否还存在。况且持续累积的无效文件无疑会浪费许多磁盘空间。

通过使用java.io.File.createTempFile()创建临时文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码public class TemporaryFileExample
{
public static void main(String[] args)
{
File temp;
try
{
temp = File.createTempFile("myTempFile", ".txt");

System.out.println("Temp file created : " + temp.getAbsolutePath());
} catch (IOException e)
{
e.printStackTrace();
}
}
}

windows系统中的输出: C:\Users\admin\AppData\Local\Temp\myTempFile7295261447112135643.txt

通过使用NIO创建临时文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码public class TemporaryFileExample
{
public static void main(String[] args)
{
try
{
final Path path = Files.createTempFile("myTempFile", ".txt");
System.out.println("Temp file : " + path);


} catch (IOException e)
{
e.printStackTrace();
}
}
}

windows系统中的输出: C:\Users\admin\AppData\Local\Temp\myTempFile3492283537103788196.txt

写入临时文件

比如在文件上传的时候,我们就可以将字节流写入临时文件当中。

使用java.io.BufferedWriter写入临时文件

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
复制代码import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class TemporaryFileExample
{
public static void main(String[] args)
{
File temp;
try
{
temp = File.createTempFile("myTempFile", ".txt");

BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
bw.write("This is the temporary data written to temp file");
bw.close();

System.out.println("Written to Temp file : " + temp.getAbsolutePath());
} catch (IOException e)
{
e.printStackTrace();
}
}
}

使用NIO写入临时文件

如果你要使用java NIO库,你就可以使用Files.write()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码public class TemporaryFileExample
{
public static void main(String[] args)
{
try
{
final Path path = Files.createTempFile("myTempFile", ".txt");
System.out.println("Temp file : " + path);


byte[] buf = "some data".getBytes();
Files.write(path, buf);

} catch (IOException e)
{
e.printStackTrace();
}
}
}

删除临时文件

删除临时文件是非常重要的一步,因为你不想让你的磁盘空间爆炸。
为了当在应用exit时(jvm终止)删除文件,你可以使用:

1
2
复制代码File temp = File.createTempFile("myTempFile", ".txt");
temp.deleteOnExit();

或者如果你想要立马删除文件,你可以直接使用delete()方法

1
2
复制代码File temp = File.createTempFile("myTempFile", ".txt");
temp.delete();

使用IO删除临时文件

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
复制代码import java.io.File;
import java.io.IOException;

public class TemporaryFileExample
{
public static void main(String[] args)
{
File temp;
try
{
temp = File.createTempFile("myTempFile", ".txt");

System.out.println("Temp file created : " + temp.getAbsolutePath());

//temp.delete(); //立即删除

temp.deleteOnExit(); //运行结束时删除

System.out.println("Temp file exists : " + temp.exists());
} catch (IOException e)
{
e.printStackTrace();
}
}
}

使用NIO删除临时文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码public class TemporaryFileExample
{
public static void main(String[] args)
{
try
{
final Path path = Files.createTempFile("myTempFile", ".txt");
System.out.println("Temp file : " + path);

// Files.delete(path); //立即删除文件

Files.deleteIfExists(path);



} catch (IOException e)
{
e.printStackTrace();
}
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

ConstraintLayout,看完一篇真的就够了么?

发表于 2019-06-27
  1. 前言

最近中毒很深,经常逛掘金,看到很多优秀的文章,感谢掘金。同时也看到很多标题,看看XXXX,一篇就够了。技术一直在不停的更新迭代,看一篇永远是不够的,建议再看一遍官网的,可以看到被作者过滤掉的信息或者最新的更新。这就是我为什么会在文末放官网链接的原因,如果有的话。

  1. ConstraintLayout

ConstraintLayout作为一款可以灵活调整view位置和大小的Viewgroup被Google疯狂推荐,以前创建布局,默认根元素都是LinearLayout,现在是ConstraintLayout了。ConstraintLayout能够以支持库的形式最小支持到API 9,同时也在不断的丰富ConstraintLayout的API和功能。ConstraintLayout在复杂布局中能够有效的,降低布局的层级,提高性能,使用更加灵活。

在app组件的Graldle默认都有如下依赖:

1
2
复制代码//可能版本不一样哦
implementation 'com.android.support.constraint:constraint-layout:1.1.3

迫不及待想了解ConstraintLayout能在布局做点什么了。

2.1 相对定位

相对定位,其实这跟RelativeLayout差不多,一个View相对另外一个View的位置。

相对布局

通过简单的使用ConstraintLayout的属性也就可以实现以上布局。World对于Hello的右边,GitCode对位于Hello的下边

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
...>
<TextView
...
android:text="Hello"
android:id="@+id/tvHello"/>

<TextView
...
android:text="World"
app:layout_constraintLeft_toRightOf="@+id/tvHello"/>

<TextView
...
android:text="GitCode"
app:layout_constraintTop_toBottomOf="@id/tvHello"/>
</android.support.constraint.ConstraintLayout>

以TextView World相对位置属性layout_constraintLeft_toRightOf来说,constraintLeft表示TextView World本身的左边,一个View有四条边,因此TextView的上、右、下边分别对应着constraintTop、constraintRight、constraintBottom。toRightOf则表示位于另外一个View的右边,例如此处位于Hello的右边,因此对应还有toLeftOf、toRghtOf、toBottomOf,分别位于View Hello的左、右、下边。总结的说,constraintXXX表示View自身约束的边,toXXXOf表示另一个View的边,而XXX的值可以是Left、Top、Right、Bottom,分别对应左,上、右、下边。layout_constraintStart_toEndOf也是类似的道理。

另外需要注意的是,view的位置可以相对于同层的view和parent,在相对于parent的时候toLeftOf、toTopOf、toRghtOf、toBottomOf分别表示位于parent的内部左上右下边缘。如图:红色框表示parent view。

再来看看一个特殊的场景:

此时想要Hello和World文本中间对齐怎么办?ConstraintLayout提供了layout_constraintBaseline_toBaselineOf属性。

1
2
3
4
5
6
7
8
9
10
复制代码<TextView
...
android:text="Hello"
android:id="@+id/tvHello"/>

<TextView
...
android:text="World"
app:layout_constraintBaseline_toBaselineOf="@id/tvHello"
app:layout_constraintLeft_toRightOf="@+id/tvHello"/>

此时界面就如愿了,比Relativelayout方便多了。

什么是baseline?贴张官网的图。

2.2 边距

边距与平常使用并无太大区别,但需要先确定view的位置,边距才会生效。如:

1
2
3
4
复制代码<TextView
...
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"/>

在其他的ViewGroup,TextView的layout_marginTop和layout_marginLeft属性是会生效的,但在ConstraintLayout不会生效,因为此时TextView的位置还没确定。下面的代码才会生效。

1
2
3
4
5
6
7
复制代码<TextView
...
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>

常用属性如下:

  • android:layout_marginStart
  • android:layout_marginEnd
  • android:layout_marginLeft
  • android:layout_marginTop
  • android:layout_marginRight
  • android:layout_marginBottom

GONE Margin

有时候,会有这种需求,在World可见的时候,GitCode与World的左边距是0,当World不见时,GitCode的左边距是某个特定的值。

World可见的效果,GitCode的左边距为0

World不可见的效果,GitCode的左边距为10

为此,ConstraintLayout提供了特殊的goneMargin属性,在目标View隐藏时,属性生效。有如下属性:

  • layout_goneMarginStart
  • layout_goneMarginEnd
  • layout_goneMarginLeft
  • layout_goneMarginTop
  • layout_goneMarginRight
  • layout_goneMarginBottom

Centering positioning and bias

在RelativeLayout居中,通常是使用以下三个属性:

  • layout_centerInParent 中间居中
  • layout_centerHorizontal 水平居中
  • layout_centerVertical 垂直居中

而在ConstraintLayout居中则采用左右上下边来约束居中。

  • 水平居中 layout_constraintLeft_toLeftOf & layout_constraintRight_toRightOf
  • 垂直居中 layout_constraintTop_toTopOf & layout_constraintBottom_toBottomOf
  • 中间居中 水平居中 & 垂直居中
    举个栗子:
1
2
3
4
5
复制代码<TextView
...
android:text="Hello"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>

效果图:

那,要是想把Hello往左挪一点,怎么办?
那很简单,使用margin呀。不不不,这里要介绍的是另外两个属性,与LinearLayout的权重类似(当然,ConstraintLayout也可以使用权重属性),但简单很多。

  • layout_constraintHorizontal_bias 水平偏移
  • layout_constraintVertical_bias 垂直偏移

两个属性的取值范围在0-1。在水平偏移中,0表示最左,1表示最右;在垂直偏移,0表示最上,1表示最下;0.5表示中间。

1
2
3
4
5
6
复制代码<TextView
...
android:text="Hello"
app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>

效果:

2.3 圆形定位(Added in 1.1)

圆形定位指的是View的中心点相对于另外View中心点的位置。贴张官网图。

涉及三个属性:

  • layout_constraintCircle : 另外一个view的id,上图的A view
  • layout_constraintCircleRadius : 半径,上图的radius
  • layout_constraintCircleAngle : 角度,上图angle,范围为0-360
    根据上面上个属性就可以确定B View的位置。从图也可以知道,角度以时间12点为0,顺时针方式。

吃个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码<TextView
...
android:text="Hello"
android:id="@+id/tvHello"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>

<TextView
android:text="World"
app:layout_constraintCircle="@id/tvHello"
app:layout_constraintCircleRadius="180dp"
app:layout_constraintCircleAngle="135"/>

效果图:Hello中间居中,World 135角度

2.4 尺寸约束

ConstraintLayout 最大最小尺寸

ConstraintLayout的宽高设为WRAP_CONTENT时,可以通过以下熟悉设置其最大最小尺寸。

  • android:minWidth 最小宽度
  • android:minHeight 最小高度
  • android:maxWidth 最大宽度
  • android:maxHeight 最大高度

ConstraintLayout中的控件尺寸约束

在ConstraintLayout中控件可以三种方式来设置其尺寸约束。

  • 指定具体的值。如123dp
  • 使用值WRAP_CONTENT,内容自适配。
  • 设为0dp,即MATCH_CONSTRAINT,扩充可用空间。

第一二种跟平常使用没什么区别。第三种会根据约束情况重新计算控件的大小。
在ConstraintLayout中,不推荐使用MATCH_PARENT,而是推荐使用MATCH_CONSTRAINT(0dp),它们的行为是类似的。

吃个栗子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码 <TextView
android:text="Hello"
android:id="@+id/tvHello"
android:gravity="center"
android:padding="20dp"
app:layout_constraintTop_toTopOf="parent"
android:textColor="@color/colorWhite"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="0dp"
android:layout_marginRight="20dp"
android:layout_height="wrap_content"/>

设置layout_width为0dp;layout_height为wrap_content;layout_marginRight为20dp,与parent左右对齐。

效果图:

在1.1之前的版本,控件尺寸设为WRAP_CONTENT,控件默认是由组件文本大小控制,其他约束是不生效的。可以通过以下属性设置是否生效。

  • app:layout_constrainedWidth=”true|false”
  • app:layout_constrainedHeight=”true|false”

控件设为MATCH_CONSTRAINT时,控件的大小会扩展所有可用空间,在1.1版本后,可以通过以下属性改变控件的行为。

  • layout_constraintWidth_min 最小宽度
  • layout_constraintHeight_min 最小高度
  • layout_constraintWidth_max 最大宽度
  • layout_constraintHeight_max 最大高度
  • layout_constraintWidth_percent 宽度占parent的百分比
  • layout_constraintHeight_percent 高度占parent的百分比

吃个栗子:

1
2
3
4
5
6
7
8
复制代码<TextView
android:text="Hello"
android:id="@+id/tvHello"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintWidth_percent="0.5"
app:layout_constraintWidth_default="percent"
android:layout_width="0dp"
android:layout_height="wrap_content"/>

将android:layout_width设为MATCH_CONSTRAINT,即0dp;将app:layout_constraintWidth_default设为percent;将app:layout_constraintWidth_percent设为0.5,表示占parent的50%,取值范围是0-1。

效果图:

比例约束

控件的宽高比,要求是宽或高至少一个设为0dp,然后设置属性layout_constraintDimensionRatio即可。

1
2
3
4
5
6
复制代码<TextView
android:text="Hello"
app:layout_constraintDimensionRatio="3:1"
android:layout_width="0dp"
android:layout_height="100dp"
/>

这里设置宽高比为3:1,高度为100dp,那么宽度将为300dp。

也可以在比例前加W,H表示是宽高比还是高宽比。如下面表示高宽比。

1
2
3
4
复制代码<Button android:layout_width="0dp" android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

2.5 链

链在水平或者垂直方向提供一组类似行为。如图所示可以理解为横向链。这里需要了解一点,A与parent的左边缘约束,B与parent的右边边缘约束,A右边和B左边之间相互约束,才能使用一条链。多个元素之间也是如此,最左最右与parent约束,元素之间边相互约束。不然下面的链式永远无法生效。

横向链最左边第一个控件,垂直链最顶边第一个控件称为链头,可以通过下面两个属性链头统一定制链的样式。

  • layout_constraintHorizontal_chainStyle 水平方向链式
  • layout_constraintVertical_chainStyle 垂直方向链式

它两的值默认可以是

  • CHAIN_SPREAD 展开样式(默认)
  • Weighted chain 在CHAIN_SPREAD样式,部分控件设置了MATCH_CONSTRAINT,那他们将扩展可用空间。
  • CHAIN_SPREAD_INSIDE 展开样式,但两端不展开
  • CHAIN_PACKED 抱团(打包)样式,控件抱团一起。通过偏移bias,可以改变packed元素的位置。

从实际开发,这么应用还是挺广泛的。
提供份代码参考,避免走冤枉路:

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
复制代码<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:text="Hello"
android:id="@+id/tvHello"
android:gravity="center"
android:padding="20dp"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/tvWorld"
android:textColor="@color/colorWhite"
android:background="@color/colorPrimaryDark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="World"
android:gravity="center"
android:padding="20dp"
android:id="@+id/tvWorld"
app:layout_constraintLeft_toRightOf="@id/tvHello"
app:layout_constraintRight_toRightOf="parent"
android:textColor="@color/colorWhite"
android:background="@color/colorPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>

效果:

在链中,剩余空余空间默认平均给各元素,但有时可以通过权重属性layout_constraintVertical_weight来指定分配空间的大小。

1.1之后,在链中使用边距时,边距是相加的,也就说,假设Hello的右边距为5,World的左边距为20,那么它们之间的边距就是25。在链式,边距先从剩余空间减去的,然后再用剩余的空间在元素之间进行定位。

2.6 优化器

在1.1之后,公开了优化器,通过在app:layout_optimizationLevel来决定控件在哪方面进行优化。

  • none : 不进行优化
  • standard : 默认方式, 仅仅优化direct和barrier约束
  • direct : 优化direct约束
  • barrier : 优化barrier约束
  • chain : 优化链约束 (实验性质)
  • dimensions : 优化尺寸 (实验性质), 减少测量次数

3.工具类

3.1 Guideline(参考线)

参考线实际上不会在界面进行显示,只是方便在ConstraintLayout布局view时候做一个参考。

通过设置Guideline的属性orientation来表示是水平方向还是垂直方向的参考线,对应值为vertical和horizontal。可以通过三种方式来定位Guideline位置。

  • layout_constraintGuide_begin 从左边或顶部指定具体的距离
  • layout_constraintGuide_end 从右边或底部指定具体的距离
  • layout_constraintGuide_percent 从宽度或高度的百分比来指定具体距离

丢个栗子:

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
复制代码<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.constraint.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/guideline"
android:orientation="vertical"
app:layout_constraintGuide_begin="10dp"/>

<Button android:text="Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
app:layout_constraintLeft_toLeftOf="@+id/guideline"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"/>

<Button android:text="Button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button2"
app:layout_constraintLeft_toLeftOf="@+id/guideline"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/button"/>
</android.support.constraint.ConstraintLayout>

Guideline设置为垂直参考线,距离开始的位置为10dp。如下图所示,实际中需要把鼠标移到button才会显示出来哦。

3.2 Barrier(栅栏)

Barrier有点类似Guideline,但Barrier会根据所有引用的控件尺寸的变化重新定位。例如经典的登录界面,右边的EditText总是希望与左右所有TextView的最长边缘靠齐。
如果两个TextView其中一个变得更长,EditText的位置都会跟这变化,这比使用RelativeLayout灵活很多。

代码:

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
复制代码<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.constraint.Barrier
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="right"
android:id="@+id/barrier"
app:constraint_referenced_ids="tvPhone,tvPassword"
/>

<TextView android:layout_width="wrap_content"
android:text="手机号码"
android:id="@+id/tvPhone"
android:gravity="center_vertical|left"
android:padding="10dp"
android:layout_height="50dp"/>

<TextView android:layout_width="wrap_content"
android:text="密码"
android:padding="10dp"
android:gravity="center_vertical|left"
android:id="@+id/tvPassword"
app:layout_constraintTop_toBottomOf="@id/tvPhone"
android:layout_height="wrap_content"/>

<EditText android:layout_width="wrap_content"
android:hint="输入手机号码"
android:id="@+id/etPassword"
app:layout_constraintLeft_toLeftOf="@id/barrier"
android:layout_height="wrap_content"/>

<EditText android:layout_width="wrap_content"
android:hint="输入密码"
app:layout_constraintTop_toBottomOf="@id/etPassword"
app:layout_constraintLeft_toLeftOf="@id/barrier"
android:layout_height="wrap_content"/>


</android.support.constraint.ConstraintLayout>

app:barrierDirection所引用控件对齐的位置,可设置的值有:bottom、end、left、right、start、top.constraint_referenced_ids为所引用的控件,例如这里的tvPhone,tvPasswrod。

3.3 Group(组)

用来控制一组view的可见性,如果view被多个Group控制,则以最后的Group定义的可见性为主。

吃个香喷喷栗子吧:
Group默认可见时,是这样的。

设置Group的visible属性为gone.

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
复制代码<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.constraint.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/group"
android:visibility="gone"
app:constraint_referenced_ids="tvPhone,tvPassword"
/>

<TextView android:layout_width="wrap_content"
android:text="手机号码"
android:id="@+id/tvPhone"
android:gravity="center_vertical|left"
android:padding="10dp"
android:layout_height="50dp"/>

<TextView android:layout_width="wrap_content"
android:text="密码"
android:padding="10dp"
android:gravity="center_vertical|left"
android:id="@+id/tvPassword"
app:layout_constraintLeft_toRightOf="@id/tvPhone"
app:layout_constraintTop_toBottomOf="@id/tvPhone"
android:layout_height="wrap_content"/>

<TextView android:layout_width="wrap_content"
android:text="GitCode"
android:padding="10dp"
android:gravity="center_vertical|left"
app:layout_constraintLeft_toRightOf="@id/tvPassword"
android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>

效果就变成了这样了,tvPhone,tvPassword都被隐藏了。

3.4 Placeholder(占位符)

一个view占位的占位符,当指定Placeholder的content属性为另一个view的id时,该view会移动到Placeholder的位置。

代码中,将TextView的定位在屏幕中间,随着将id设置给Placeholder的属性后,TextView的位置就跑到Placeholder所在的地方,效果图跟上图一直。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.constraint.Placeholder
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:content="@id/tvGitCode"
/>

<TextView android:layout_width="wrap_content"
android:text="GitCode"
android:id="@+id/tvGitCode"
android:padding="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:gravity="center_vertical|left"
android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>

3.5 其他

在2.0,为ConstraintLayout增加了ConstraintProperties、ConstraintsChangedListener等,感兴趣可以自己看看官网。

更多信息请参考官网

4.总结

在写本文之前,其实还不会用ConstraintLayout,写完本文之后,已经上手和喜欢上了,满足自己在实际开发中想要的效果,能够有效的减少布局的层级,从而提高性能。不知道看完本文,你会使用ConstraintLayout了没有?

Welcom to visit my github

好文推荐:
Jetpack:在数据变化时如何优雅更新Views数据

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

探究MySQL各类文件

发表于 2019-06-26

前言

前两篇我们了解到MySQL的整体架构,其分为了四层,包括网络连接层,核心层,存储引擎层,物理层,以及各层的作用。另外还知道了InnoDB存储引擎层的架构,包括缓存池和线程。

不明白的,请移步上两篇哦。今天我们来看构成MySQL数据库和InnoDB存储引擎表的各类文件。

参数文件

告诉MySQL实例启动时在哪里可以找到数据库文件,以及指定某些初始化参数,如某种内存结构的大小设置。

那上面是参数呢?简单来说,可以把数据库参数看成一个键值对(key/value),比如innodb_buffer_pool_size=1G。那这些键值对都存放在哪里了呢?答案揭晓,my.ini里面存储了大量键值对(key/value),且位于MySQL的安装目录,如下图。

我们打开这个文件看一下,里面都是键值对的形式。如果我们想要修改某个值,直接在这边修改,并且重启即可。

日志文件

用来记录MySQL实例对某种条件做出响应时写入的文件,包括如下四种。

错误日志

记录MySQL的启动,运行和关闭中的错误信息。我们先来看一下文件存储的位置。

我们去相应路径下查看该文件,可以发现出错信息。所以当MySQL数据库不能正常启动的时候,第一个必须查找的文件就是错误日志文件。

二进制日志

记录对MySQL数据库进行更改的所有操作,但是不包括select和show这类操作,因为这类操作对数据本身没有影响。然而,如果是update或delete这种操作,即使对数据库没有造成影响,那么也会被记录到二进制日志中。

这段是不是特别拗口,不咋能理解,没事,我们实际操作下。

首先二进制日志文件在默认情况下并没有启动,需要手动指定参数来启动。这是不是就意味着开启这个选项会对数据库的整体性能有影响?但是根据MySQL官方手册中的测试表明,开启二进制日志会使性能下降1%,是可接受的范围。

是否下降1%,这个数字我们也无从考证,但是要晓得一点,就是会有影响,但问题不大。毕竟要在每次insert,delete,update操作后,记录日志,这肯定是要时间和空间的损耗的。

那么开始表演吧。

首先,我们先开启二进制配置,上面参数文件有说过my.ini里面存储的各种配置信息的键值对,只需要在该文件里面添加log-bin,指定了log的名称为主机名-bin,后缀为序列号,所在的路径为数据库所在的路径。

接着,重启服务器,步骤如下,重启之后,我们可以看到相应的文件。

最后,在准备工作好了之后,我们先来查询test2的数据,然后看一下那两个文件的大小有没有变化,很明显大小还是跟之前的一样。

再来写一个update语句,我们可以看到影响的行数为0,但是那两个文件的大小有增加了。

InnoDB的存储引擎文件

表空间

InnoDB将存储的数据按表空间进行存放。在默认配置中会有一个初始大小为10MB,名为ibdata1的文件,其为所有表的表空间。当然也可以通过innodb_file_per_table设置每个表一个独立的表空间,命名规则为.ibd。是否很熟悉?没错,这就是我们上篇说到的innodb表存储在硬盘的文件之一。

重做日志文件

如果断电时,InnoDB会通过重做日志来恢复到断电前的状态,保证数据的完整性。

每个InnoDB存储引擎至少有一个重做日志组group,和两个文件ib_logfile0和ib_logfile1。

他们两个大小一样,并且以循环写入的方式运行,即先写重做日志1,等到写满时,会切换到重做日志2,等到日志2写满时,再次切换到重做日志1。

结束

码字不易,请多多关注哦。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【肥朝】面试官问我,Redis分布式锁如何续期?懵了。

发表于 2019-06-26

前言

上一篇面试官问我,使用Dubbo有没有遇到一些坑?我笑了。之后,又有一位粉丝和我说在面试过程中被虐了.鉴于这位粉丝是之前肥朝的**老粉丝**,而且周一又要开启新一轮的面试,为了回馈他长期以来的支持,所以连夜写了本篇,希望能对他接下来的面试有所帮助.

真实案例

Redis分布式锁的正确姿势

据肥朝了解,很多同学在用分布式锁时,都是直接百度搜索找一个Redis分布式锁工具类就直接用了.关键是该工具类中还充斥着很多System.out.println();等语句.其实Redis分布式锁比较正确的姿势是采用redisson这个客户端工具.具体介绍可以搜索最大的同性交友网站github.

如何回答

首先如果你之前用Redis的分布式锁的姿势正确,并且看过相应的官方文档的话,这个问题So easy.我们来看

坦白说,如果你英文棒棒哒那么看英文文档可能更好理解

By default lock watchdog timeout is 30 seconds and can be changed through Config.lockWatchdogTimeout setting.

但是你如果看的是中文文档

看门狗检查锁的超时时间默认是30秒

这句话肥朝从语文角度分析就是一个歧义句,他有两个意思

1.看门狗默认30秒去检查一次锁的超时时间

2.看们狗会去检查锁的超时时间,锁的时间时间默认是30秒

看到这里,我希望大家不要黑我的小学体育老师,虽然他和语文老师是同个人.语文不行,我们可以源码来凑!

源码分析

我们根据官方文档给出的例子,写了一个最简单的demo,例子根据上面截图中Ctr+C和Ctr+V一波操作,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码public class DemoMain {

public static void main(String[] args) throws Exception {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");

RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("anyLock");

lock.lock();
//lock.unlock();
}
}

create

从这里我们知道,internalLockLeaseTime 和 lockWatchdogTimeout这两个参数是相等的.

lockWatchdogTimeout默认值如下

1
2
3
4
5
6
7
8
9
10
复制代码public class Config {

private long lockWatchdogTimeout = 30 * 1000;

public long getLockWatchdogTimeout() {
return lockWatchdogTimeout;
}

//省略无关代码
}

从internalLockLeaseTime这个单词也可以看出,这个加的分布式锁的超时时间默认是30秒.但是还有一个问题,那就是这个看门狗,多久来延长一次有效期呢?我们往下看

lock

从我图中框起来的地方我们就知道了,获取锁成功就会开启一个定时任务,也就是watchdog,定时任务会定期检查去续期renewExpirationAsync(threadId).

这里定时用的是netty-common包中的HashedWheelTimer,肥朝公众号已经和各大搜索引擎建立了密切的合作关系,你只需要把这个类在任何搜索引擎一搜,都能知道相关API参数的意义.

从图中我们明白,该定时调度每次调用的时间差是internalLockLeaseTime / 3.也就10秒.

真相大白

通过源码分析我们知道,默认情况下,加锁的时间是30秒.如果加锁的业务没有执行完,那么到 30-10 = 20秒的时候,就会进行一次续期,把锁重置成30秒.那这个时候可能又有同学问了,那业务的机器万一宕机了呢?宕机了定时任务跑不了,就续不了期,那自然30秒之后锁就解开了呗.

写在最后

如果你是肥朝公众号的老粉丝,并且在面试、工作过程中遇到了什么问题,欢迎来撩.但是肥朝是个正经的Java开发,我们只调接口,不调情!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

获取视频截图

发表于 2019-06-25

最近在搞一个视频截图的功能,自己在探索过程中,看到很多各种各样的解法,走了很多弯路,为了避免做相同功能的朋友们走很多弯路,我把自己解决方案,及探索过程遇到的Bug记录下来

screenVideo是一个通用的视频截图工具,目前已经适配大部分机型,对于个别机型不能使用的欢迎issuses,Demo中的视频的url不可用,视频的url可以从任意视频网站找一个可以播放的视频地址,用火狐获取一下播放的URL,替换demo中的url即可 Github下载

最开始想的是直接用View截图的方式截取当前的视频,结果截取的来的图片是黑屏,附上View截图代码

1
2
3
4
5
6
7
8
复制代码public  Bitmap convertViewToBitmap(View view){
view.destroyDrawingCache();//销毁旧的cache销毁,获取cache通常会占用一定的内存,所以通常不需要的时候有必要对其进行清理
view.setDrawingCacheEnabled(true);//cache开启
view.buildDrawingCache();//创建新的缓存,获取cache通常会占用一定的内存,所以通常不需要的时候有必要对其进行清理,在每次获取新的,先销毁旧的缓存
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));//测量view
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());//将缓存的view转换为图片
return bitmap;
}

上面的方式只能对静态的View进行截图,但是动态的比如说视频,那么截出来的图片就是黑屏。
用SurfaceView显示在线视频,然后通过上面截图方式,得到图片是黑屏,(关于黑屏的原因大家可以去网上搜索,可以得到你想要的答案,这里就不在说了)于是我就去谷歌,各大博客上寻求解决方案,发现Android提供了MediaMetadataRetriever这个类来获取缩放图,于是按照这个思路去搜索,发现可以通过获取能够获取当前播放的帧数,来进行截图,以下是我的最终解决方案

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
复制代码/**
* 视频截图代码
* @param url 播放的url
* @param width 生成图片的宽度
* @param height 生成图片的高度
* @param currentVideoTime 当前播放的播放的秒数
* @return
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private Bitmap createVideoThumbnail(String url, int width, int height,String currentVideoTime) {
Bitmap bitmap = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
int kind = MediaStore.Video.Thumbnails.MINI_KIND;
try {
if (Build.VERSION.SDK_INT >= 14) {//Android4.0以上的设备,必须使用这种方式来设置源播放视频的路径
retriever.setDataSource(url, new HashMap<String, String>());
} else {
retriever.setDataSource(url);
}
int millis = mMdeiaPlayer.getDuration();
Log.e(TAG, "-----millis----" + millis);
int pro = mMdeiaPlayer.getCurrentPosition();
Log.e(TAG,"-----pro----"+pro);
String timeString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
long time = Long.parseLong(timeString) * 1000; //获取总长度,这一句也是必须的
long d = time*pro/millis;//计算当前播放的帧数,来截取当前的视频
Log.e(TAG,"---------"+d);
bitmap = retriever.getFrameAtTime(d, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
if (kind == MediaStore.Images.Thumbnails.MICRO_KIND && bitmap != null) {
bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
}
} catch (IllegalArgumentException ex) {
// Assume this is a corrupt video file
} catch (RuntimeException ex) {
// Assume this is a corrupt video file.
} finally {
try {
retriever.release();
mMdeiaPlayer.start();
} catch (RuntimeException ex) {
// Ignore failures while cleaning up.
}
}

return bitmap;
}

Github下载地址 : https://github.com/hi-dhl/screenVideo

bug及解决方案

start called in state 4

1
2
3
4
5
复制代码04-05 10:58:14.169 2237-2237/demo.dhl.con.onlinevideo E/MediaPlayer: start called in state 4
04-05 10:58:14.169 2237-2237/demo.dhl.con.onlinevideo E/MediaPlayer: error (-38, 0)
04-05 10:58:14.169 2237-2237/demo.dhl.con.onlinevideo E/MediaPlayer: Error (-38,0)
04-05 10:58:14.176 2237-2250/demo.dhl.con.onlinevideo E/MediaPlayer: error (261, -1003)
04-05 10:58:14.176 2237-2237/demo.dhl.con.onlinevideo E/MediaPlayer: Error (261,-1003)

可能由于的播放的文件错误,或者给的url地址不能播放,可以在浏览器中试一下。

start called in state 1

1
2
3
4
复制代码04-05 11:50:27.346 2038-2038/demo.dhl.con.onlinevideo E/MediaPlayer: start called in state 1
04-05 11:50:27.347 2038-2038/demo.dhl.con.onlinevideo E/MediaPlayer: error (-38, 0)
04-05 11:50:27.367 2038-2050/demo.dhl.con.onlinevideo E/MediaPlayer: error (261, -1003)
04-05 11:50:27.367 2038-2038/demo.dhl.con.onlinevideo E/MediaPlayer: Error (261,-1003)

原因:

1
2
复制代码Streaming is Not supported before Android 3.0
Please test in device having above 3.0 version

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码![](http://upload-images.jianshu.io/upload_images/1479838-d821c0b136e94734.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这是Android官网关于,Android所支持的网络协议网络协议
>音频和视频播放支持以下网络协议:
>RTSP协议(RTP,SDP的)
>HTTP / HTTPS的进步流
>HTTP / HTTPS的现场直播议定书草案:
>MPEG-2 TS流媒体文件只
>协议版本3(的Android 4.0及以上)
>议定书“第2版(Android的3.x版)
>不支持之前的Android 3.0
>注: HTTPS不支持之前的Android 3.1。
更换一台Android3.0以上的设备就好了

status=0x80000000

1
复制代码java.io.IOException: setDataSource failed.: status=0x80000000
  • 第一种解决方法
1
复制代码InputStream in = getResources().getAssets().open("GPSResp.dat");
  • 第二种解决方案

将播放的视频或者音乐因为转换成Android所支持的格式
下面是Android所支持的格式

1
2
3
4
5
6
7
8
复制代码static const char* kNoCompressExt[] = {
".jpg", ".jpeg", ".png", ".gif",
".wav", ".mp2", ".mp3", ".ogg", ".aac",
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
".amr", ".awb", ".wma", ".wmv"
};

java.io.IOException: Prepare failed.: status=0x1

MediaPlay播放视频的时候报下面的错

1
复制代码    java.io.IOException: Prepare failed.: status=0x1

解决方案:把mediaPlayer.prepare;改成 mediaPlayer.prepareAsync();

getFrameAtTime: videoFrame is a NULL pointer

播放视频的时候包下面的错误

1
复制代码    getFrameAtTime: videoFrame is a NULL pointer

解决方案:视频地址错误,或者 视频损坏不能播放,检查视频是否正常

怎么样对播放的视频进行截图

当我们使用SurfaceView的来显示播放的视频的时候,需要截取视频的时候,直接使用普通View获取截图的方式,会是黑屏,网上很多博客提到了解决方案mHolder.lockCanvas() 获取Canva来获取画布,实现截取视频,其实是错误的,我照着网上的贴子做了,报了下面的错,不知道是不是我的使用方法有错,请网友指正

1
2
3
4
5
复制代码    12:58:24.690: E/BaseSurfaceHolder(719): Exception locking surface
12:58:24.690: E/BaseSurfaceHolder(719): java.lang.IllegalArgumentException
12:58:24.690: E/BaseSurfaceHolder(719): at android.view.Surface.nativeLockCanvas(Native Method)
12:58:24.690: E/BaseSurfaceHolder(719): at android.view.Surface.lockCanvas(Surface.java:447)
12:58:24.690: E/BaseSurfaceHolder(719): at com.android.internal.view.BaseSurfaceHolder.internalLockCanvas(BaseSurfaceHolder.java:184)

原因:
SurfaceView 主要用来两种用法:

  1. 和MediaPlay配合使用播放视频,
  2. 或者和Canvas配合使用实现一些动画

但是不能这两种方法一起使用或者就会报上面的错。

解决方案:如果想要做视频截取的话,可以使用MediaMetadataRetriever这个类截取当前播放的帧画面,来是现实视频截图功能,项目贴上,代码中有注释

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

缓冲池(buffer pool),这次彻底懂了!!!

发表于 2019-06-25

应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库。

操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问。

MySQL作为一个存储系统,同样具有缓冲池(buffer pool)机制,以避免每次查询数据都进行磁盘IO。

今天,和大家聊一聊InnoDB的缓冲池。

InnoDB的缓冲池缓存什么?有什么用?

缓存表数据与索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用。

速度快,那为啥不把所有数据都放到缓冲池里?

凡事都具备两面性,抛开数据易失性不说,访问快速的反面是存储容量小:

(1)缓存访问快,但容量小,数据库存储了200G数据,缓存容量可能只有64G;

(2)内存访问快,但容量小,买一台笔记本磁盘有2T,内存可能只有16G;

因此,只能把“最热”的数据放到“最近”的地方,以“最大限度”的降低磁盘访问。

如何管理与淘汰缓冲池,使得性能最大化呢?

在介绍具体细节之前,先介绍下“预读”的概念。

什么是预读?

磁盘读写,并不是按需读取,而是按页读取,一次至少读一页数据(一般是4K),如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率。

预读为什么有效?

数据访问,通常都遵循“集中读写”的原则,使用一些数据,大概率会使用附近的数据,这就是所谓的“局部性原理”,它表明提前加载是有效的,确实能够减少磁盘IO。

按页(4K)读取,和InnoDB的缓冲池设计有啥关系?

(1)磁盘访问按页读取能够提高性能,所以缓冲池一般也是按页缓存数据;

(2)预读机制启示了我们,能把一些“可能要访问”的页提前加入缓冲池,避免未来的磁盘IO操作;

InnoDB是以什么算法,来管理这些缓冲页呢?

最容易想到的,就是LRU(Least recently used)。

画外音:memcache,OS都会用LRU来进行页置换管理,但MySQL的玩法并不一样。

传统的LRU是如何进行缓冲页管理?

最常见的玩法是,把入缓冲池的页放到LRU的头部,作为最近访问的元素,从而最晚被淘汰。这里又分两种情况:

(1)页已经在缓冲池里,那就只做“移至”LRU头部的动作,而没有页被淘汰;

(2)页不在缓冲池里,除了做“放入”LRU头部的动作,还要做“淘汰”LRU尾部页的动作;

如上图,假如管理缓冲池的LRU长度为10,缓冲了页号为1,3,5…,40,7的页。

假如,接下来要访问的数据在页号为4的页中:

(1)页号为4的页,本来就在缓冲池里;

(2)把页号为4的页,放到LRU的头部即可,没有页被淘汰;

画外音:为了减少数据移动,LRU一般用链表实现。

假如,再接下来要访问的数据在页号为50的页中:

(1)页号为50的页,原来不在缓冲池里;

(2)把页号为50的页,放到LRU头部,同时淘汰尾部页号为7的页;

传统的LRU缓冲池算法十分直观,OS,memcache等很多软件都在用,MySQL为啥这么矫情,不能直接用呢?

这里有两个问题:

(1)预读失效;

(2)缓冲池污染;

什么是预读失效?

由于预读(Read-Ahead),提前把页放入了缓冲池,但最终MySQL并没有从页中读取数据,称为预读失效。

如何对预读失效进行优化?

要优化预读失效,思路是:

(1)让预读失败的页,停留在缓冲池LRU里的时间尽可能短;

(2)让真正被读取的页,才挪到缓冲池LRU的头部;

以保证,真正被读取的热数据留在缓冲池里的时间尽可能长。

具体方法是:

(1)将LRU分为两个部分:

  • 新生代(new sublist)
  • 老生代(old sublist)

(2)新老生代收尾相连,即:新生代的尾(tail)连接着老生代的头(head);

(3)新页(例如被预读的页)加入缓冲池时,只加入到老生代头部:

  • 如果数据真正被读取(预读成功),才会加入到新生代的头部
  • 如果数据没有被读取,则会比新生代里的“热数据页”更早被淘汰出缓冲池

举个例子,整个缓冲池LRU如上图:

(1)整个LRU长度是10;

(2)前70%是新生代;

(3)后30%是老生代;

(4)新老生代首尾相连;

假如有一个页号为50的新页被预读加入缓冲池:

(1)50只会从老生代头部插入,老生代尾部(也是整体尾部)的页会被淘汰掉;

(2)假设50这一页不会被真正读取,即预读失败,它将比新生代的数据更早淘汰出缓冲池;

假如50这一页立刻被读取到,例如SQL访问了页内的行row数据:

(1)它会被立刻加入到新生代的头部;

(2)新生代的页会被挤到老生代,此时并不会有页面被真正淘汰;

改进版缓冲池LRU能够很好的解决“预读失败”的问题。

画外音:但也不要因噎废食,因为害怕预读失败而取消预读策略,大部分情况下,局部性原理是成立的,预读是有效的。*

新老生代改进版LRU仍然解决不了缓冲池污染的问题。

什么是MySQL缓冲池污染?

当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染。

例如,有一个数据量较大的用户表,当执行:

select * from user where name like “%shenjian%”;

虽然结果集可能只有少量数据,但这类like不能命中索引,必须全表扫描,就需要访问大量的页:

(1)把页加到缓冲池(插入老生代头部);

(2)从页里读出相关的row(插入新生代头部);

(3)row里的name字段和字符串shenjian进行比较,如果符合条件,加入到结果集中;

(4)…直到扫描完所有页中的所有row…

如此一来,所有的数据页都会被加载到新生代的头部,但只会访问一次,真正的热数据被大量换出。

怎么这类扫码大量数据导致的缓冲池污染问题呢?

MySQL缓冲池加入了一个“老生代停留时间窗口”的机制:

(1)假设T=老生代停留时间窗口;

(2)插入老生代头部的页,即使立刻被访问,并不会立刻放入新生代头部;

(3)只有满足“被访问”并且“在老生代停留时间”大于T,才会被放入新生代头部;

继续举例,假如批量数据扫描,有51,52,53,54,55等五个页面将要依次被访问。

如果没有“老生代停留时间窗口”的策略,这些批量被访问的页面,会换出大量热数据。

加入“老生代停留时间窗口”策略后,短时间内被大量加载的页,并不会立刻插入新生代头部,而是优先淘汰那些,短期内仅仅访问了一次的页。

而只有在老生代呆的时间足够久,停留时间大于T,才会被插入新生代头部。

上述原理,对应InnoDB里哪些参数?

有三个比较重要的参数。

参数:innodb_buffer_pool_size

介绍:配置缓冲池的大小,在内存允许的情况下,DBA往往会建议调大这个参数,越多数据和索引放到内存里,数据库的性能会越好。

参数:innodb_old_blocks_pct

介绍:老生代占整个LRU链长度的比例,默认是37,即整个LRU中新生代与老生代长度比例是63:37。

画外音:如果把这个参数设为100,就退化为普通LRU了。

参数:innodb_old_blocks_time

介绍:老生代停留时间窗口,单位是毫秒,默认是1000,即同时满足“被访问”与“在老生代停留时间超过1秒”两个条件,才会被插入到新生代头部。

总结

(1)缓冲池(buffer pool)是一种常见的降低磁盘访问的机制;

(2)缓冲池通常以页(page)为单位缓存数据;

(3)缓冲池的常见管理算法是LRU,memcache,OS,InnoDB都使用了这种算法;

(4)InnoDB对普通LRU进行了优化:

  • 将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题
  • 页被访问,且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题

思路,比结论重要。

解决了什么问题,比方案重要。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

为什么阿里巴巴要求谨慎使用ArrayList中的subLis

发表于 2019-06-25

GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗?

GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗?

GitHub 3.7k Star 的Java工程师成神之路 ,真的确定不来了解一下吗?

集合是Java开发日常开发中经常会使用到的。在之前的一些文章中,我们介绍过一些关于使用集合类应该注意的事项,如《为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作》、《为什么阿里巴巴建议集合初始化时,指定集合容量大小》等。

关于集合类,《阿里巴巴Java开发手册》中其实还有另外一个规定:

-w1379


本文就来分析一下为什么会有如此建议?其背后的原理是什么?

subList

subList是List接口中定义的一个方法,该方法主要用于返回一个集合中的一段、可以理解为截取一个集合中的部分元素,他的返回值也是一个List。

如以下代码:

1
2
3
4
5
6
7
8
9
10
复制代码public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("Hollis");
add("hollischuang");
add("H");
}};

List subList = names.subList(0, 1);
System.out.println(subList);
}

以上代码输出结果为:

1
复制代码[Hollis]

如果我们改动下代码,将subList的返回值强转成ArrayList试一下:

1
2
3
4
5
6
7
8
9
10
复制代码public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("Hollis");
add("hollischuang");
add("H");
}};

ArrayList subList = names.subList(0, 1);
System.out.println(subList);
}

以上代码将抛出异常:

1
复制代码java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

不只是强转成ArrayList会报错,强转成LinkedList、Vector等List的实现类同样也都会报错。

那么,为什么会发生这样的报错呢?我们接下来深入分析一下。

底层原理

首先,我们看下subList方法给我们返回的List到底是个什么东西,这一点在JDK源码中注释是这样说的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive.

也就是说subList 返回是一个视图,那么什么叫做视图呢?

我们看下subList的源码:

1
2
3
4
复制代码public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}

这个方法返回了一个SubList,这个类是ArrayList中的一个内部类。

SubList这个类中单独定义了set、get、size、add、remove等方法。

当我们调用subList方法的时候,会通过调用SubList的构造函数创建一个SubList,那么看下这个构造函数做了哪些事情:

1
2
3
4
5
6
7
8
复制代码SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}

可以看到,这个构造函数中把原来的List以及该List中的部分属性直接赋值给自己的一些属性了。

也就是说,SubList并没有重新创建一个List,而是直接引用了原有的List(返回了父类的视图),只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。

所以,为什么不能讲subList方法得到的集合直接转换成ArrayList呢?因为SubList只是ArrayList的内部类,他们之间并没有集成关系,故无法直接进行强制类型转换。

视图有什么问题

前面通过查看源码,我们知道,subList()方法并没有重新创建一个ArrayList,而是返回了一个ArrayList的内部类——SubList。

这个SubList是ArrayList的一个视图。

那么,这个视图又会带来什么问题呢?我们需要简单写几段代码看一下。

1、非结构性改变SubList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码public static void main(String[] args) {
List<String> sourceList = new ArrayList<String>() {{
add("H");
add("O");
add("L");
add("L");
add("I");
add("S");
}};

List subList = sourceList.subList(2, 5);

System.out.println("sourceList : " + sourceList);
System.out.println("sourceList.subList(2, 5) 得到List :");
System.out.println("subList : " + subList);

subList.set(1, "666");

System.out.println("subList.set(3,666) 得到List :");
System.out.println("subList : " + subList);
System.out.println("sourceList : " + sourceList);

}

得到结果:

1
2
3
4
5
6
复制代码sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.set(3,666) 得到List :
subList : [L, 666, I]
sourceList : [H, O, L, 666, I, S]

当我们尝试通过set方法,改变subList中某个元素的值得时候,我们发现,原来的那个List中对应元素的值也发生了改变。

同理,如果我们使用同样的方法,对sourceList中的某个元素进行修改,那么subList中对应的值也会发生改变。读者可以自行尝试一下。

1、结构性改变SubList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码public static void main(String[] args) {
List<String> sourceList = new ArrayList<String>() {{
add("H");
add("O");
add("L");
add("L");
add("I");
add("S");
}};

List subList = sourceList.subList(2, 5);

System.out.println("sourceList : " + sourceList);
System.out.println("sourceList.subList(2, 5) 得到List :");
System.out.println("subList : " + subList);

subList.add("666");

System.out.println("subList.add(666) 得到List :");
System.out.println("subList : " + subList);
System.out.println("sourceList : " + sourceList);

}

得到结果:

1
2
3
4
5
6
复制代码sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.add(666) 得到List :
subList : [L, L, I, 666]
sourceList : [H, O, L, L, I, 666, S]

我们尝试对subList的结构进行改变,即向其追加元素,那么得到的结果是sourceList的结构也同样发生了改变。

1、结构性改变原List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码public static void main(String[] args) {
List<String> sourceList = new ArrayList<String>() {{
add("H");
add("O");
add("L");
add("L");
add("I");
add("S");
}};

List subList = sourceList.subList(2, 5);

System.out.println("sourceList : " + sourceList);
System.out.println("sourceList.subList(2, 5) 得到List :");
System.out.println("subList : " + subList);

sourceList.add("666");

System.out.println("sourceList.add(666) 得到List :");
System.out.println("sourceList : " + sourceList);
System.out.println("subList : " + subList);

}

得到结果:

1
2
3
4
5
6
7
8
9
复制代码Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
at java.util.AbstractCollection.toString(AbstractCollection.java:454)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at com.hollis.SubListTest.main(SubListTest.java:28)

我们尝试对sourceList的结构进行改变,即向其追加元素,结果发现抛出了ConcurrentModificationException。关于这个异常,我们在《一不小心就踩坑的fail-fast是个什么鬼?》中分析过,这里原理相同,就不再赘述了。

小结

我们简单总结一下,List的subList方法并没有创建一个新的List,而是使用了原List的视图,这个视图使用内部类SubList表示。

所以,我们不能把subList方法返回的List强制转换成ArrayList等类,因为他们之间没有继承关系。

另外,视图和原List的修改还需要注意几点,尤其是他们之间的相互影响:

1、对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。

2、对子List做结构性修改,操作同样会反映到父List上。

3、对父List做结构性修改,会抛出异常ConcurrentModificationException。

所以,阿里巴巴Java开发手册中有另外一条规定:



如何创建新的List

如果需要对subList作出修改,又不想动原list。那么可以创建subList的一个拷贝:

1
2
复制代码subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());

PS:最近,《阿里巴巴Java开发手册》已经正式更名为《Java开发手册》,并发布了新版本,增加了21条新规约,修改描述112处。

关注公众号后台回复:手册,即可获取最新版Java开发手册。

参考资料: www.jianshu.com/p/585485124… www.cnblogs.com/ljdblog/p/6…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Jetpack Lifeccyle入门指南

发表于 2019-06-25

为什么需要我们管理Activity和Fragment的生命周期?这些不是Framework自动帮我们搞定的么?(手动黑人问号)刚看到这样的标题我也是很懵逼,不就是onCreate->onSart()->onResume()->onPause()->onStop()->onDestory()么?难道还有什么高深的地方么?

题外话

在讲Data Bing Library的文章中,大多数同学反馈在实际开发中没什么必要。因为没深爱过,没法名正言顺为其正名。恰好周末逛到郭霖公众号一篇讲Data Bing 在RecyclerView的实践,讲的挺好,大家有空移步前去阅读。引用《卖油翁的故事》道理->熟能生巧,爱得够深,就能擦出火花。

如果你对Data Binding Library有独特的见解或看法,可以前往Data Binding Library(数据绑定库)讨论

Abount Lifecycle-Aware Components

该组件能在像Activity、Fragment等等具有生命周期的组件发生状态改变时,以轻量级的和易维护的代码作出响应动作。

吃个栗子就懂意思了:

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
复制代码//为了考虑大多数同学学习过Java,就贴Java的代码
//定义个监听类,用来在Activity生命周期发生变化时,对定位服务资源作相关处理
class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}

void start() {
// connect to system location service
}

void stop() {
// disconnect from system location service
}
}
//在Activity中回调监听类相关方法
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;

@Override
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}

@Override
public void onStart() {
super.onStart();
myLocationListener.start();
// manage other components that need to respond
// to the activity lifecycle
}

@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
// manage other components that need to respond
// to the activity lifecycle
}
}

毕竟拿人手短,吃人嘴软,吃了人家的栗子就得给人家分析。

这个栗子还是挺简单的,只是简单调用了MyLocationListener对象的两个回调方法,但是在实际开发中,大多数情况下会在Activity的onCreate()、onStart()等等生命周期中做大量的业务逻辑处理和UI更新,而且如果不止一个监听类需要被回调的话,就意味着要在Activity中管理多个其他组件的生命周期或者回调。那代码的维护性和可读性就非常差。(另外,在发生某种竞态条件的情况下,可能会导致Activity的onStop()方法在onStart()方法前就发生了)

为了解决这种痛点,在包android.arch.lifecycle 中提供类和接口来独立的管理Activity和Fragment组件的生命周期。

Lifecycle

生命周期是一个类包含组件的生命周期状态的信息(例如Activity和Fragment),并允许其他对象观察到这个状态。
Lifecycle对象以两种主要的枚举类型跟踪它关联组件的生命周期状态:

  • Event
    Framework和Lifecycle类分发生命周期事件(Lifecycle Event),并映射到Activity或者Fragment的回调事件。
  • State Lifecycle对象跟踪组件的当前状态。

生命周期状态

方框中的INITALZIED、DESTROYED等等表示组件的状态,而箭头上的ON_CREATE、ON_START等等则表示事件。假如Framework或者Lifecycle类分发ON_CREATE事件,表示关联的组件的状态从初始化到onCreate状态,对应调用组件的onCreate()方法。
通过在类的方法上添加注解来监听组件的生命周期。然后将该类以观察者形式添加到具有生命周期的类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码//MyObserver作为一个观察者来监听有生命周期的类
public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void connectListener() {
...
}

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void disconnectListener() {
...
}
}
//myLifecycleOwner是一个实现了LifecycleOwner 接口的类,继续看下文
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

LifecycleOwner

LifecycleOwner,是只有一个getLifecycle()方法的接口,实现该接口的类表示具有生命周期。如果想管理整个应用的生命周期,用 ProcessLifecycleOwner代替LifecycleOwner。LifecycleOwner接口将所有具有生命周期的类的生命周期所有权抽象出来,以方便可以在其生命周期进行读写。实现了LifecycleObserver接口的类可以注册到实现了LifecycleOwner的类,以观察对应组件的生命周期。例如上文的:

1
复制代码myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

这样做带来的好处是什么?
通过这种观察者模式,可以将平常根据Activity或Fragment生命周期状态的逻辑分离到单独类中进行处理,以便更好的逻辑开发、功能迭代和后期维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;

public void onCreate(...) {
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}

讲到这里基本就已经知道如何使用Lifecycle-Aware Components管理Activity或Fragment的生命周期了。如果LifecycleObserver需要监听其他Activity或Fragment的生命周期,只需要重新初始化并注册到新的Activity或Fragment即可。资源的设置和清除回收不需要我们担心。

Custom LifecycleOwner

在Support Library 26.1.0和更高版本,Activity和Fragment已经默认实现了LifecycleOwner。如果要实现定制的LifecycleOwner,需要新建LifecycleRegistry对象,并将相关事件传递给它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry lifecycleRegistry;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

lifecycleRegistry = new LifecycleRegistry(this);
lifecycleRegistry.markState(Lifecycle.State.CREATED);
}

@Override
public void onStart() {
super.onStart();
lifecycleRegistry.markState(Lifecycle.State.STARTED);
}

@NonNull
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}

最好实践

  • 保持UI(Activity或Fragment)精简。不要在Activity或Fragment中取获取应用数据,采用ViewModel或者Livedata。也就是为什么有MVP、MVVM模式。
  • 尝试写数据驱动类型UI。在数据发生变化的时候通过UI控制器更新UI或者通知用户进行操作。
  • 把数据的处理逻辑放到ViewModel类中。ViewModel作为UI界面与数据的桥梁,处理数据与UI界面交互的逻辑。注意不是获取数据的逻辑,例如获取网络数据或者数据库数据。
  • 使用数据绑定库(Data Binding Library)在UI界面和UI控制器之间进行维护。这样有利于减少在Activity或Fragment中更新UI的代码。在Java中可以使用Butter Knife库。
  • 如果UI界面很复杂,可以建立一个主持(Presenter)类去处理视图。MVP模式?
  • 使用Kotlin协同机制管理耗时任务。

使用lifecycle-aware components的场景

lifecycle-aware组件在不同情况能让我们更方便的管理Acitivity和Fragment的生命周期。那在什么情景下适合使用该组件呢?

  • 粗细粒度的切换。例如在定位中,如果当前界面可见,那么定位精度应该更细,定位请求更频繁,以提高响应性。当切换到后台时,请求定位的频率就要放缓,以降低功耗
  • 开始和停止视频缓冲。这个是高手,在应用加载的时候尽早缓冲视频,减少用户等待时间在应用退出是结束缓冲。
  • 开始和停止网络连接。根据App状态自动切换连接的状态。
  • 开始和停止绘制图片。应用在后台时不会绘制,返回前台继续绘制。

存在的一些问题

当Fragment或AppCompatActivity在调用onSaveInstanceState()方法保存状态后,它们的视图在ON_START事件被调用之前是不会改变的,在这期间UI被更改会引起不一致的问题,这就是为什么FragmentManager在状态保存后运行FragmentTransaction会抛出异常。

因此如果AppCompatActivity的onStop()方法在调用onSaveInstanceState()方法之后,就会造成一个缺口:UI状态改变是不允许,但生命周期还没有改为CREATED的状态。为此,Lifecycle类通过将状态标记为CREATED,但直到调用onStop()方法前,不分发该事件,此时去检测当前的状态也可获得真实的值。但是还存在以下两个问题:

  • 在Android 6.0和更低版本,系统调用onSaveInstanceState()方法,但它不一定调用onStop()方法。这样就会造成事件无法分发,潜在的导致观察者以为lifecycle处于活动状态,尽管此时它处于停止状态。
  • 任何想要在LiveData暴露类似行为的类必须实现Lifecycle 版本 beta 2 和更低版本提供的方法。

更多信息请参考官网

总结

本文是对官网知识的理解或翻译,建议再进一步阅读原文,毕竟原文才是原汁原味,知识点也多。
从第一次看官网懵懵懂懂,到开始了解,又掌握一个知识点。不仅光讲Lifecycle-Aware Components的知识点,还讲到平常开发应用的最佳实践,这些对实际开发都有很强指导作用。

Welcom to visit my github

本文是Jetpack系列文章第二篇

第一篇:
Jetpack:你还在findViewById么?

第三篇:
Jetpack:在数据变化时如何优雅更新Views数据

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…867868869…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%