vue 配置

1、在 package.json 中配置

2、在根目录,新建 vue.config.js

1
2
3
4
5
modeule.exports = {
devServer: {
port: 8888
}
}

示例:

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
module.exports = {
baseUrl: '/', // 部署应用时的根路径(默认'/'),也可用相对路径(存在使用限制)
outputDir: 'dist', // 运行时生成的生产环境构建文件的目录(默认''dist'',构建之前会被清除)
assetsDir: '', //放置生成的静态资源(s、css、img、fonts)的(相对于 outputDir 的)目录(默认'')
indexPath: 'index.html',//指定生成的 index.html 的输出路径(相对于 outputDir)也可以是一个绝对路径。
pages: { //pages 里配置的路径和文件名在你的文档目录必须存在 否则启动服务会报错
index: { //除了 entry 之外都是可选的
entry: 'src/index/main.js', // page 的入口,每个“page”应该有一个对应的 JavaScript 入口文件
template: 'public/index.html', // 模板来源
filename: 'index.html', // 在 dist/index.html 的输出
title: 'Index Page', // 当使用 title 选项时,在 template 中使用:<title><%= //htmlWebpackPlugin.options.title %></title>
chunks: ['chunk-vendors', 'chunk-common', 'index'] // 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk
},
subpage: 'src/subpage/main.js'//官方解释:当使用只有入口的字符串格式时,模板会被推导为'public/subpage.html',若找不到就回退到'public/index.html',输出文件名会被推导为'subpage.html'
},
lintOnSave: true,// 是否在保存的时候检查
productionSourceMap: true,// 生产环境是否生成 sourceMap 文件
css: {
extract: true,// 是否使用css分离插件 ExtractTextPlugin
sourceMap: false,// 开启 CSS source maps
loaderOptions: {},// css预设器配置项
modules: false// 启用 CSS modules for all css / pre-processor files.
},
devServer: {// 环境配置
host: 'localhost',
port: 8080,
https: false,
hotOnly: false,
open: true, //配置自动启动浏览器
proxy: {// 配置多个代理(配置一个 proxy: 'http://localhost:4000' )
'/api': {
target: '<url>',
ws: true,
changeOrigin: true
},
'/foo': {
target: '<other_url>'
}
}
},
pluginOptions: {// 第三方插件配置
// ...
}
};

3、 babel.config.js

Mysql 8 安装

Ubuntu 20.04 可运行 sudo apt isntall mysql-server 命令直接安装。

1
2
CREATE USER 'pma'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'pmapass';
GRANT SELECT, INSERT, UPDATE, DELETE ON <pma_db>.* TO 'pma'@'localhost';

下载:

https://dev.mysql.com/downloads/

https://dev.mysql.com/downloads/mysql/

文档:

https://dev.mysql.com/doc/refman/8.0/en/

https://dev.mysql.com/doc/refman/8.0/en/binary-installation.html

https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/

安装(https://dev.mysql.com/doc/refman/8.0/en/binary-installation.html#binary-installation-layout):

推荐安装在 /usr/local 目录

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
tar -xvf  mysql_8.tar.xz //解压 tar.xz 文件

mv mysql_8 mysql //重命名

cd mysql

mkdir data //数据目录

groupadd mysql
useradd mysql -g mysql

chown mysql:mysql data


bin/mysqld --initialize --user=mysql --datadir /usr/local/mysql/data // 生成 root 用户的初始密码

cp support-files/mysql.server /etc/init.d/

vi .bash_profile //编辑环境变量
:/usr/local/mysql/bin //在 PATH 后添加

source .bash_profile


mysql -u root -p

//修改初始密码
//5.7 set password=password('密码');
alter user user() identified by '密码';

https://www.cnblogs.com/keme/p/10288168.html

APT 安装:

https://blog.csdn.net/wm609972715/article/details/83759266

如何在Ubuntu 18.04中安装MySQL 8.0数据库服务器_数据库技术_Linux公社-Linux系统门户网站 (linuxidc.com)

Ubuntu中更改MySQL数据库文件目录:

1
2
3
4
5
6
7
8
9
service mysql stop

cd /media/data
cp -a /var/lib/mysql /mysql

# 修改配置文件
vi /etc/mysql/mysql.conf.d/mysqld.cnf
# 修改 datadir
datadir = /media/data/mysql
1
2
3
4
5
6
vim /etc/apparmor.d/usr.sbin.mysqld

# /var/lib/mysql r, 修改为
/media/data/mysql r,
# /var/lib/mysql/** rwk, 修改为
/data/media/mysql/** rwk,
1
2
service apparmor restart
service mysql restart

https://www.jb51.net/article/150089.htm

使用Supervisor管理进程

Supervisor 是一款使用 Python 开发的非常优秀的进程管理工具。它可以在类 UNIX 系统上让用户精确地监视与控制多组指定数量的服务进程。当监控的服务进程意外退出时,会尝试自动重启这些服务,以保持服务可用状态。

安装

Supervisor 官方 提供的安装方式较多,这里采用 pip 方式安装。

安装pip

1
$ yum install python-pip# 升级pip$ pip install --upgrade pip$ pip -Vpip 9.0.1

安装Supervisor

通过 pip 安装 Supervisor:

1
$ pip install supervisorSuccessfully installed supervisor-3.3.3

安装 Supervisor 后,会出现 supervisorctl 和 supervisord 两个程序,其中 supervisorctl 为服务监控终端,而 supervisord 才是所有监控服务的大脑。查看 supervisord 是否安装成功:

1
$ supervisord -v3.3.3

开机启动

将 supervisord 配置成开机启动服务,下载官方 init 脚本

修改关键路径配置:

1
2
3
PIDFILE=/var/run/supervisord.pid
LOCKFILE=/var/lock/subsys/supervisord
OPTIONS="-c /etc/supervisord.conf"

移到该文件到/etc/init.d目录下,并重命名为 supervisor,添加可执行权限:

1
$ chmod 777 /etc/init.d/supervisor

配置成开机启动服务:

1
2
3
4
$ chkconfig --add supervisor
$ chkconfig supervisor on
$ chkconfig --list | grep "supervisor"
supervisor 0:off 1:off 2:on 3:on 4:on 5:on 6:off

配置

生成配置文件

Supervisord 安装后,需要使用如下命令生成配置文件。

1
2
$ mkdir /etc/supervisor
$ echo_supervisord_conf > /etc/supervisor/supervisord.conf

主配置部分

supervisord.conf的主配置部分说明:

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
[unix_http_server]
file=/tmp/supervisor.sock ; socket文件的路径
;chmod=0700 ; socket文件权限
;chown=nobody:nogroup ; socket文件用户和用户组
;username=user ; 连接时认证的用户名
;password=123 ; 连接时认证的密码

[inet_http_server] ; 监听TCP
port=127.0.0.1:9001 ; 监听ip和端口
username=user ; 连接时认证的用户名
password=123 ; 连接时认证的密码

[supervisord]
logfile=/var/log/supervisord.log ; log目录
logfile_maxbytes=50MB ; log文件最大空间
logfile_backups=10 ; log文件保持的数量
loglevel=info ; log级别
pidfile=/var/run/supervisord.pid
nodaemon=false ; 是否非守护进程态运行
minfds=1024 ; 系统空闲的最少文件描述符
minprocs=200 ; 可用的最小进程描述符
;umask=022 ; 进程创建文件的掩码
;identifier=supervisor ; supervisord标识符
;directory=/tmp ; 启动前切换到的目录
;nocleanup=true ; 启动前是否清除子进程的日志文件
;childlogdir=/tmp ; AUTO模式,子进程日志路径
;environment=KEY="value" ; 设置环境变量

[rpcinterface:supervisor] ; XML_RPC配置
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; 连接的socket路径
;username=chris ; 用户名
;password=123 ; 密码
prompt=mysupervisor ; 输入用户名和密码时的提示符
;history_file=~/.sc_history ; 历史操作记录存储路径

[include] ; 包含文件,将每个进程配置为一个文件并包含
files = /etc/supervisor/*.ini ; 多个进程的配置文件

这部分我们不需要做太多的配置修改,如果需要开启 WEB 终端监控,则需要配置并开启 inet_http_server 项。

进程配置部分

Supervisor 需管理的进程服务配置,示例如下:

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
[program:work]                      ; 服务名,例如work
command=php -r "sleep(10);exit(1);" ; 带有参数的可执行命令
process_name=%(process_num)s ; 进程名,当numprocs>1时,需包含%(process_num)s
numprocs=2 ; 启动进程的数目数
;directory=/tmp ; 运行前切换到该目录
;umask=022 ; 进程掩码
;priority=999 ; 子进程启动关闭优先级
autostart=true ; 子进程是否被自动启动
startsecs=1 ; 成功启动几秒后则认为成功启动
;startretries=3 ; 子进程启动失败后,最大尝试启动的次数
autorestart=unexpected ; 子进程意外退出后自动重启的选项,false, unexpected, true。unexpected表示不在exitcodes列表时重启
exitcodes=0,2 ; 期待的子程序退出码
;stopsignal=QUIT ; 进程停止信号,可以为TERM,HUP,INT,QUIT,KILL,USR1,or USR2等信号,默认为TERM
;stopwaitsecs=10 ; 发送停止信号后等待的最大时间
;stopasgroup=false ; 是否向子进程组发送停止信号
;killasgroup=false ; 是否向子进程组发送kill信号
;redirect_stderr=true ; 是否重定向日志到标准输出
stdout_logfile=/data/logs/work.log ; 进程的stdout的日志路径
;stdout_logfile_maxbytes=1MB ; 日志文件最大大小
;stdout_logfile_backups=10
;stdout_capture_maxbytes=1MB
;stderr_logfile=/a/path ; stderr的日志路径
;stderr_logfile_maxbytes=1MB
;stderr_logfile_backups=10
;stderr_capture_maxbytes=1MB
;environment=A="1",B="2" ; 子进程的环境变量
;serverurl=AUTO ; 子进程的环境变量SUPERVISOR_SERVER_URL

通常将每个进程的配置信息配置成独立文件,并通过 include 模块包含,这样方便修改和管理配置文件。

启动

配置完成后,启动 supervisord 守护服务:

1
$ supervisord -c /etc/supervisor/supervisord.conf

常用的命令参数说明:

  • -c:指定配置文件路径
  • -n:是否非守护态运行
  • -l:日志文件目录
  • -i:唯一标识

查看 supervisord 启动情况:

1
2
3
4
$ ps -ef | grep "supervisor"
root 24901 1 0 Sep23 ? 00:00:30 /usr/bin/python /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
$ netstat -tunpl
tcp 0 0 127.0.0.1:9001 0.0.0.0:* LISTEN 24901/python

监控进程

Supervisor 提供了多种监控服务的方式,包括 supervisorctl 命令行终端、Web 端、XML_RPC 接口多种方式。

命令终端

直接使用 supervisorctl 即可在命令行终端查看所有服务的情况,如下:

1
2
3
4
$ supervisorctl 
work:0 RUNNING pid 31313, uptime 0:00:07
work:1 RUNNING pid 31318, uptime 0:00:06
# -u 用户名 -p 密码

supervisorctl 常用命令列表如下;

  • status:查看服务状态
  • update:重新加载配置文件
  • restart:重新启动服务
  • stop:停止服务
  • pid:查看某服务的 pid
  • tail:输出最新的 log 信息
  • shutdown:关闭 supervisord 服务

Web

在配置中开启 inet_http_server 后,即可通过 Web 界面便捷地监控进程服务了。

来源:

https://www.fanhaobai.com/2017/09/supervisor.html

16 Magic Methods That PHP Developers Must Know

The methods which begin with two underscores (__) are called Magic Methods in PHP, and they play an important role. Magic methods include:

Method Name Description
__construct() the constructor of a class
__destruct() the destructor of a class
__call($funName, $arguments) The __call() method will be called when an undefined or inaccessible method is called.
__callStatic($funName, $arguments) The __callStatic() method will be called when an undefined or inaccessible static method is called.
__get($propertyName) The __get() method will be called when getting a member variable of a class.
__set($property, $value) The __set() method will be called when setting a member variable of a class.
__isset($content) The __isset() method will be called when calling isset() or empty() for an undefined or inaccessible member.
__unset($content) The __unset() method will be called when calling reset() for an undefined or inaccessible member.
__sleep() The __sleep() method will be called first when executing serialize().
__wakeup() The __wakeup() method will be called first when deserialization() is executed.
__toString() The __toString() method will be called when using echo method to print an object directly.
__invoke() The __invoke() method will be called when trying to call an object in a way of calling function.
__set_state($an_array) The __set_state() method will be called when calling var_export().
__clone() The __clone() method will be called when the object is copied.
__autoload($className) Try to load an undefined class.
__debugInfo() Print debug information.

In this article, we will show you how to use these PHP magic methods with some examples.

1.__construct()

The PHP constructor is the first method that is automatically called after the object is created. Each class has a constructor. If you do not explicitly declare it, then there will be a default constructor with no parameters and empty content in the class.

  1. the use of the method

Constructors are usually used to perform some initialization tasks, such as setting initial values for member variables when creating objects.

  1. the declaration format of the constructor in a class
1
2
3
4
5
function __constrct([parameter list]){

Method body //It's usually used to set initial values for member variables

}

Note: Only one constructor can be declared in the same class, because PHP does not support constructor overloading.

Here is a complete example:

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
<?php
class Person
{
public $name;
public $age;
public $sex;

/**
* explicitly declare a constructor with parameters
*/
public function __construct($name="", $sex="Male", $age=22)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}

/**
* say method
*/
public function say()
{
echo "Name:" . $this->name . ",Sex:" . $this->sex . ",Age:" . $this->age;
}

}

Create object $Person1 without any parameters.

1
2
$Person1 = new Person();
echo $Person1->say(); //print:Name:,Sex:Male,Age:22

Create object $Person2 with parameter “Jams”.

1
2
$Person2 = new Person("Jams");
echo $Person2->say(); // print: Name: Jams, Sex: Male, Age: 22

Create object $Person3 with three parameters.

1
2
$Person3 = new Person ("Jack", "Male", 25);
echo $Person3->say(); // print: Name: Jack, Sex: Male, Age: 25

2. __destruct()

Now that we have already known what the constructor is, the destructor is the opposite.

Destructor allows you to perform some operations before destroying an object, such as closing a file, emptying a result set, and so on.

Destructor is a new feature introduced by PHP5.

The declaration format of the destructor is similar to that of the constructor __construct(), which means that __destruct() is also started with two underscores, and the name of the destructor is also fixed.

  1. the declaration format of destructor
1
2
3
4
function __destruct()
{
//method body
}

Note: Destructor cannot have any parameters.

  1. the use of the destructor

In general, the destructor is not very common in PHP. It’s an optional part of a class, usually used to complete some cleanup tasks before the object is destroyed.

Here is an example of using the destructor:

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
<?php
class Person{

public $name;
public $age;
public $sex;

public function __construct($name="", $sex="Male", $age=22)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}

/**
* say method
*/
public function say()
{
echo "Name:".$this->name.",Sex:".$this->sex.",Age:".$this->age;
}

/**
* declare a destructor method
*/
public function __destruct()
{
echo "Well, my name is ".$this->name;
}
}

$Person = new Person("John");
unset($Person); //destroy the object of $Person created above

The output of the program above is:

1
Well, my name is John

3. __call()

This method takes two parameters. The first parameter $function_name will automatically receive the undefined method name, while the second $arguments will receive multiple arguments of the method as an array.

1)The use of __call() method

1
2
3
4
function __call(string $function_name, array $arguments)
{
// method body
}

When an undefined method is called in a program, the __call() method will be called automatically.

Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Person
{
function say()
{
echo "Hello, world!<br>";
}

function __call($funName, $arguments)
{
echo "The function you called:" . $funName . "(parameter:" ; // Print the method's name that is not existed.
print_r($arguments); // Print the parameter list of the method that is not existed.
echo ")does not exist!!<br>\n";
}
}
$Person = new Person();
$Person->run("teacher"); // If the method which is not existed is called within the object, then the __call() method will be called automatically.
$Person->eat("John", "apple");
$Person->say();

The results are as follows:

1
2
3
The function you called: run (parameter: Array([0] => teacher)) does not exist!
The function you called: eat (parameter: Array([0] => John[1] => apple)) does not exist!
Hello world!

4. __callStatic()

When an undefined static method is called in a program, the __callStatic() method will be called automatically.

The use of __callStatic() is similar to the __call(). Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Person
{
function say()
{
echo "Hello, world!<br>";
}

public static function __callStatic($funName, $arguments)
{
echo "The static method you called:" . $funName . "(parameter:" ; // Print the method's name that is not existed.
print_r($arguments); // Print the parameter list of the method that is not existed.
echo ")does not exist!<br>\n";
}
}
$Person = new Person();
$Person::run("teacher"); // If the method which is not existed is called within the object, then the __callStatic() method will be called automatically.
$Person::eat("John", "apple");
$Person->say();

The results are as follows:

1
2
3
The static method you called: run (parameter: Array([0] => teacher)) does not exist!
The static method you called: eat (parameter: Array([0] => John[1] => apple)) does not exist!
Hello world!

5. __get()

When you try to access a private property of an external object in a program, the program will throw an exception and end execution. We can use the magic method __get() to solve this problem. It can get the value of the private property of the object outside the object. Here is an example:

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
<?php
class Person
{
private $name;
private $age;

function __construct($name="", $age=1)
{
$this->name = $name;
$this->age = $age;
}

public function __get($propertyName)
{
if ($propertyName == "age") {
if ($this->age > 30) {
return $this->age - 10;
} else {
return $this->$propertyName;
}
} else {
return $this->$propertyName;
}
}
}
$Person = new Person("John", 60); // Instantiate the object with the Person class and assign initial values to the properties with the constructor.
echo "Name:" . $Person->name . "<br>"; // When the private property is accessed, the __get() method will be called automatically,so we can get the property value indirectly.
echo "Age:" . $Person->age . "<br>"; // The __get() method is called automatically,and it returns different values according to the object itself.

The results are as follows:

1
2
Name: John
Age: 50

6. __set()

The __set( $property, $value) method is used to set the private property of the object. When an undefined property is assigned, the __set() method will be triggered and the passed parameters are the property name and value that are set.

Here is the demo code:

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
<?php
class Person
{
private $name;
private $age;

public function __construct($name="", $age=25)
{
$this->name = $name;
$this->age = $age;
}

public function __set($property, $value) {
if ($property=="age")
{
if ($value > 150 || $value < 0) {
return;
}
}
$this->$property = $value;
}

public function say(){
echo "My name is ".$this->name.",I'm ".$this->age." years old";
}
}

$Person=new Person("John", 25); //Note that the initial value will be changed by the code below.
$Person->name = "Lili"; //The "name" property is assigned successfully. If there is no __set() method, then the program will throw an exception.
$Person->age = 16; //The "age" property is assigned successfully.
$Person->age = 160; //160 is an invalid value, so it fails to be assigned.
$Person->say(); //print:My name is Lili, I'm 16 years old.

Here is the result:

1
My name is Lili, I'm 16 years old

7. __isset()

Before using the __isset () method, let me explain the use of the isset () method first. The isset () method is mainly used to determine whether the variable is set.

If you use the isset() method outside the object, there are two cases:

  1. If the parameter is a public property, you can use the isset() method to determine whether the property is set or not.
  2. If the parameter is a private property , the isset() method will not work.

So for the private property, is there any way to know if it is set? Of course, as long as we define a __isset() method in a class, we can use the isset() method outside the class to determine whether the private property is set or not.

When the isset() or empty() is called on an undefined or inaccessible property, the __isset() method will be called. Here is an example:

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
<?php
class Person
{
public $sex;
private $name;
private $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

/**
* @param $content
*
* @return bool
*/
public function __isset($content) {
echo "The {$content} property is private,the __isset() method is called automatically.<br>";
echo isset($this->$content);
}
}

$person = new Person("John", 25); // Initially assigned.
echo isset($person->sex),"<br>";
echo isset($person->name),"<br>";
echo isset($person->age),"<br>";

The results are as follows:

1
2
3
4
5
1
The name property is private,the __isset() method is called automatically.
1
The age property is private,the __isset() method is called automatically.
1

8. __unset()

Similar to the __isset() method, the __unset() method is called when the unset() method is called on an undefined or inaccessible property. Here is an example:

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
<?php
class Person
{
public $sex;
private $name;
private $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

/**
* @param $content
*
* @return bool
*/
public function __unset($content) {
echo "It is called automatically when we use the unset() method outside the class.<br>";
echo isset($this->$content);
}
}

$person = new Person("John", 25); // Initially assigned.
unset($person->sex),"<br>";
unset($person->name),"<br>";
unset($person->age),"<br>";

The results are as follows:

1
2
3
4
It is called automatically when we use the unset() method outside the class.
1
It is called automatically when we use the unset() method outside the class.
1

9. __sleep()

The serialize() method will check if there is a magic method __sleep() in the class. If it exists, the method will be called first and then perform the serialize operation.

The __sleep() method is often used to specify the properties that need to be serialized before saving data. If there are some very large objects that don’t need to be saved all, then you will find this feature is very useful.

For details, please refer to the following code:

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
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

/**
* @return array
*/
public function __sleep() {
echo "It is called when the serialize() method is called outside the class.<br>";
$this->name = base64_encode($this->name);
return array('name', 'age'); // It must return a value of which the elements are the name of the properties returned.
}
}

$person = new Person('John'); // Initially assigned.
echo serialize($person);
echo '<br/>';

The results are as follows:

1
2
It is called when the serialize() method is called outside the class.
O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}

10. __wakeup()

In contrast to the __sleep() method, the __wakeup() method is often used in deserialize operations, such as re-building a database connection, or performing other initialization operations.

Here is an example:

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
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

/**
* @return array
*/
public function __sleep() {
echo "It is called when the serialize() method is called outside the class.<br>";
$this->name = base64_encode($this->name);
return array('name', 'age'); // It must return a value of which the elements are the name of the properties returned.
}

/**
* __wakeup
*/
public function __wakeup() {
echo "It is called when the unserialize() method is called outside the class.<br>";
$this->name = 2;
$this->sex = 'Male';
// There is no need to return an array here.
}
}

$person = new Person('John'); // Initially assigned.
var_dump(serialize($person));
var_dump(unserialize(serialize($person)));

The results are as follows:

1
2
3
4
It is called when the serialize() method is called outside the class.
string(58) "O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}"
It is called when the unserialize() method is called outside the class.
object(Person)#2 (3) { ["sex"]=> string(3) "Male" ["name"]=> int(2) ["age"]=> int(25) }

11. __toString()

The __toString() method will be called when using echo method to print an object directly.

Note: This method must return a string, otherwise it will throw a fatal error at E_RECOVERABLE_ERROR level. And you also can’t throw an exception in the __toString() method.

Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

public function __toString()
{
return 'go go go';
}
}

$person = new Person('John'); // Initially assigned.
echo $person;

The results are as follows:

1
go go go

So what if the __toString() method is not defined in the class? Let’s have a try.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

}

$person = new Person('John'); // Initially assigned.
echo $person;

The results are as follows:

1
Catchable fatal error: Object of class Person could not be converted to string in D:\phpStudy\WWW\test\index.php on line 18

Obviously, It reports a fatal error on the page, which is not allowed by PHP syntax.

12. __invoke()

When you try to call an object in the way of calling a function, the __ invoke() method will be called automatically.

Note: This feature is only valid in PHP 5.3.0 and above.

Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

public function __invoke() {
echo 'This is an object';
}

}

$person = new Person('John'); // Initially assigned.
$person();

Here is the result:

1
This is an object

If you insist on using objects as methods(but not defining the __invoke() method),then you will get the following result:

1
Fatal error: Function name must be a string in D:\phpStudy\WWW\test\index.php on line 18

13.__set_state()

Starting from PHP 5.1.0, the __set_state() method is called automatically when calling var_export() to export the class code.

The parameters of the __set_state() method is an array containing the values of all the properties, with the format of array(‘property’ => value,…)

We don’t define __set_state() method in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

}

$person = new Person('John'); // Initially assigned.
var_export($person);

Here is the result:

1
Person::__set_state(array( 'sex' => 'Male', 'name' => 'John', 'age' => 25, ))

Obviously, the properties of the object are printed.

Now let’s have a look at the other case of being defined the __set_state() method:

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
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

public static function __set_state($an_array)
{
$a = new Person();
$a->name = $an_array['name'];
return $a;
}

}

$person = new Person('John'); // Initially assigned.
$person->name = 'Jams';
var_export($person);

Here is the result:

1
Person::__set_state(array( 'sex' => 'Male', 'name' => 'Jams', 'age' => 25, ))

14. __clone()

In PHP we can use the clone keyword to copy objects with the following syntax:

1
$copy_of_object = clone $object;

However, using clone keyword is just a shallow copy, because all referenced properties will still point to the original variable.

If a __clone() method is defined in a object, then the __clone() method will be called in the copy-generated object and can be used to modify the value of the property (if necessary).

Here is an example:

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
<?php
class Person
{
public $sex;
public $name;
public $age;

public function __construct($name="", $age=25, $sex='Male')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

public function __clone()
{
echo __METHOD__."your are cloning the object.<br>";
}

}

$person = new Person('John'); // Initially assigned.
$person2 = clone $person;

var_dump('persion1:');
var_dump($person);
echo '<br>';
var_dump('persion2:');
var_dump($person2);

The results are as follows:

1
2
3
Person::__clone your are cloning the object.
string(9) "persion1:" object(Person)#1 (3) { ["sex"]=> string(3) "Male" ["name"]=> string(6) "John" ["age"]=> int(25) }
string(9) "persion2:" object(Person)#2 (3) { ["sex"]=> string(3) "Male" ["name"]=> string(6) "John" ["age"]=> int(25) }

15.__autoload()

The __autoload() method can try to load an undefined class.

In the past, if you were to create 100 objects in a program file, then you had to use include() or require() to contain 100 class files, or you had to define the 100 classes in the same class file, for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* file non_autoload.php
*/

require_once('project/class/A.php');
require_once('project/class/B.php');
require_once('project/class/C.php');
.
.
.

if (ConditionA) {
$a = new A();
$b = new B();
$c = new C();
// …
} else if (ConditionB) {
$a = newA();
$b = new B();
// …
}

So what if we use the __autoload() method?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* file autoload_demo.php
*/
function __autoload($className) {
$filePath = “project/class/{$className}.php”;
if (is_readable($filePath)) {
require($filePath);
}
}

if (ConditionA) {
$a = new A();
$b = new B();
$c = new C();
// …
} else if (ConditionB) {
$a = newA();
$b = new B();
// …
}

When the PHP engine uses class A at the first time, if class A is not found, then the __autoload method will be called automatically and the class name “A” will be passed as a parameter. So what we need to do in the __autoload() method is to find the corresponding class file based on the class name and then include the file. If the file is not found, then the php engine will throw an exception.

16. __debugInfo()

The __debugInfo() method will be called when the var_dump() method is executed. If the __debugInfo() method is not defined, then the var_dump() method will print out all the properties in the object.

Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class C {
private $prop;

public function __construct($val) {
$this->prop = $val;
}

/**
* @return array
*/
public function __debugInfo() {
return [
'propSquared' => $this->prop ** 2,
];
}
}

var_dump(new C(42));

Here is the result:

1
object(C)#1 (1) { ["propSquared"]=> int(1764) }

Note: The __debugInfo() method should be applied to the PHP 5.6.0 and above.

Summarize

The above are the PHP magic methods that I understand, of which commonly used include __set(), __get() and __autoload(). If you still have any questions, you can get more help from the official PHP website.

来源:

https://www.tutorialdocs.com/article/16-php-magic-methods.html

https://mp.weixin.qq.com/s/9Gj5sXumLuWZh5Ua3xzmqg

Practical ProGuard rules examples

In my previous article I explained why everyone should use ProGuard for their Android apps, how to enable it and what kind of errors you might encounter when doing so. There was a lot of theory involved, as I think it’s important to understand the underlying principles in order to be prepared to deal with any potential problems.

I also talked in a separate article about the very specific problem of configuring ProGuard for an Instant App build.

In this part, I’d like to talk about the practical examples of ProGuard rules on a medium sized sample app: Plaid by Nick Butcher.

Lessons learned from Plaid

Plaid actually turned out to be a great subject for researching ProGuard problems, as it contains a mix of 3rd party libraries that use things like annotation processing and code generation, reflection, java resource loading and native code (JNI). I extracted and jotted down some practical advice that should apply to other apps in general:

Data classes

1
2
3
4
5
public class User {
String name;
int age;
...
}

Probably every app has some kind of data class (also known as DMOs, models, etc. depending on context and where they sit in your app’s architecture). The thing about data objects is that usually at some point they will be loaded or saved (serialized) into some other medium, such as network (an HTTP request), a database (through an ORM), a JSON file on disk or in a Firebase data store.

Many of the tools that simplify serializing and deserializing these fields rely on reflection. GSON, Retrofit, Firebase — they all inspect field names in data classes and turn them into another representation (for example: {“name”: “Sue”, “age”: 28}), either for transport or storage. The same thing happens when they read data into a Java object — they see a key-value pair “name”:”John” and try to apply it to a Java object by looking up a String name field.

Conclusion: We cannot let ProGuard rename or remove any fields on these data classes, as they have to match the serialized format. It’s a safe bet to add a @Keep annotation on the whole class or a wildcard rule on all your models:

1
-keep class io.plaidapp.data.api.dribbble.model.** { *; }

Warning*: It’s possible to make a mistake when testing if your app is susceptible to this issue. For example, if you serialize an object to JSON and save it to disk in version N of your app without the proper keep rules, the saved data might look like this:* *{“a”: “Sue”, “b”: 28}*. Because ProGuard renamed your fields to *a* and *b*, everything will seem to work, data will be saved and loaded correctly.

However, when you build your app again and release version N+1 of your app, ProGuard might decide to rename your fields to something different, such as *c* and *d*. As a result, data saved previously will fail to load.

You *must* ensure you have the proper keep rules in the first place.

Java code called from native side (JNI)

Android’s default ProGuard files (you should always include them, they have some really useful rules) already contain a rule for methods that are implemented on the native side (-keepclasseswithmembernames class * { native ; }). Unfortunately there is no catch-all way to keep code invoked in the opposite direction: from JNI into Java.

With JNI it’s entirely possible to construct a JVM object or find and call a method on a JVM handle from C/C++ code and in fact, one of the libraries used in Plaid does that.

Conclusion: Because ProGuard can only inspect Java classes, it will not know about any usages that happen in native code. We must explicitly retain such usages of classes and members via a @Keep annotation or -keep rule.

1
2
3
4
-keep, includedescriptorclasses 
class in.uncod.android.bypass.Document { *; }
-keep, includedescriptorclasses
class in.uncod.android.bypass.Element { *; }

Opening resources from JAR/APK

Android has its own resources and assets system that normally shouldn’t be a problem for ProGuard. However, in plain Java there is another mechanism for loading resources straight from a JAR file and some third-party libraries might be using it even when compiled in Android apps (in that case they will try to load from the APK).

The problem is that usually these classes will look for resources under their own package name (which translates to a file path in the JAR or APK). ProGuard can rename package names when obfuscating, so after compilation it might happen that the class and its resource file are no longer in the same package in the final APK.

To identify loading resources in this way, you can look for calls to Class.getResourceAsStream / getResource and ClassLoader.getResourceAsStream / getResource in your code and in any third party libraries you depend on.

Conclusion: We should keep the name of any class that loads resources from the APK using this mechanism.

In Plaid, there are actually two — one in the OkHttp library and one in Jsoup:

1
2
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-keepnames class org.jsoup.nodes.Entities

How to come up with rules for third party libraries

In an ideal world, every dependency you use would supply their required ProGuard rules in the AAR. Sometimes they forget to do this or only publish JARs, which don’t have a standardized way to supply ProGuard rules.

In that case, before you start debugging your app and coming up with rules, remember to check the documentation. Some library authors supply recommended ProGuard rules (such as Retrofit used in Plaid) which can save you a lot of time and frustration. Unfortunately, many libraries don’t (such as is the case with Jsoup and Bypass mentioned in this article). Also be aware that in some cases the config supplied with the library will only work with optimizations disabled, so if you are turning them on you might be in uncharted territory.

So how to come up with rules when the library doesn’t supply them?
I can only give you some pointers:

  1. Read the build output and logcat!
  2. Build warnings will tell you which -dontwarn rules to add
  3. ClassNotFoundException, MethodNotFoundException and FieldNotFoundException will tell you which -keep rules to add

You should be glad when your app crashes with ProGuard enabled — you’ll have somewhere to start your investigation :)

The worst class of problems to debug are when you app works, but for example doesn’t show a screen or doesn’t load data from the network.

That’s where you need to consider some of the scenarios I described in this article and get your hands dirty, even diving into the third party code and understanding why it might fail, such as when it uses reflection, introspection or JNI.

Debugging and stack traces

ProGuard will by default remove many code attributes and hidden metadata that are not required for program execution . Some of those are actually useful to the developer — for example, you might want to retain source file names and line numbers for stack traces to make debugging easier:

1
-keepattributes SourceFile, LineNumberTable

You should also remember to save the ProGuard mappings files produced when you build a release version and upload them to Play to get de-obfuscated stack traces from any crashes experienced by your users.

If you are going to attach a debugger to step through method code in a ProGuarded build of your app, you should also keep the following attributes to retain some debug information about local variables (you only need this line in your debug build type):

1
-keepattributes LocalVariableTable, LocalVariableTypeTable

Minified debug build type

The default build types are configured such that debug doesn’t run ProGuard. That makes sense, because we want to iterate and compile fast when developing, but still want the release build to use ProGuard to be as small and optimized as possible.

But in order to fully test and debug any ProGuard problems, it’s good to set up a separate, minified debug build like this:

1
2
3
4
5
6
7
8
9
10
buildTypes {
debugMini {
initWith debug
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
matchingFallbacks = ['debug']
}
}

With this build type, you’ll be able to connect the debugger, run UI tests (also on a CI server) or monkey test your app for possible problems on a build that’s as close to your release build as possible.

Conclusion: When you use ProGuard you should always QA your release builds thoroughly, either by having end-to-end tests or manually going through all screens in your app to see if anything is missing or crashing.

Runtime annotations, type introspection

ProGuard will by default remove all annotations and even some surplus type information from your code. For some libraries that’s not a problem — those that process annotations and generate code at compile time (such as Dagger 2 or Glide and many more) might not need these annotations later on when the program runs.

There is another class of tools that actually inspect annotations or look at type information of parameters and exceptions at runtime. Retrofit for example does this by intercepting your method calls by using a Proxy object, then looking at annotations and type information to decide what to put or read from the HTTP request.

Conclusion: Sometimes it’s required to retain type information and annotations that are read at runtime, as opposed to compile time. You can check out the attributes list in the ProGuard manual.

1
-keepattributes *Annotation*, Signature, Exception

If you’re using the default Android ProGuard configuration file (*getDefaultProguardFile('proguard-android.txt')*), the first two options — Annotations and Signature — are specified for you. If you’re not using the default you have to make sure to add them yourself (it also doesn’t hurt to just duplicate them if you know they’re a requirement for your app).

Moving everything to the default package

The -repackageclasses option is not added by default in the ProGuard config. If you are already obfuscating your code and have fixed any problems with proper keep rules, you can add this option to further reduce DEX size. It works by moving all classes to the default (root) package, essentially freeing up the space taken up by strings like “com.example.myapp.somepackage”.

1
-repackageclasses

ProGuard optimizations

As I mentioned before, ProGuard can do 3 things for you:

  1. it gets rid of unused code,
  2. renames identifiers to make the code smaller,
  3. performs whole program optimizations.

The way I see it, everyone should try and configure their build to get 1. and 2. working.

To unlock 3. (additional optimizations), you have to use a different default ProGuard configuration file. Change the proguard-android.txt parameter to proguard-android-optimize.txt in your build.gradle:

1
2
3
4
5
6
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}

This will make your release build slower, but will potentially make your app run faster and reduce code size even further, thanks to optimizations such as method inlining, class merging and more aggressive code removal. Be prepared however, that it might introduce new and difficult to diagnose bugs, so use it with caution and if anything isn’t working, be sure to disable certain optimizations or disable the use of the optimizing config altogether.

In the case of Plaid, ProGuard optimizations interfered with how Retrofit uses Proxy objects without concrete implementations, and stripped away some method parameters that were actually required. I had to add this line to my config:

1
-optimizations !method/removal/parameter

You can find a list of possible optimizations and how to disable them in the ProGuard manual.

When to use @Keep and -keep

@Keep support is actually implemented as a bunch of -keep rules in the default Android ProGuard rules file, so they’re essentially equivalent. Specifying -keep rules is more flexible as it offers wildcards, you can also use different variants which do slightly different things (-keepnames, -keepclasseswithmembers and more).

Whenever a simple “keep this class” or “keep this method” rule is needed though, I actually prefer the simplicity of adding a@Keep annotation on the class or member, as it stays close to the code, almost like documentation.

If some other developer coming after me wants to refactor the code, they will know immediately that a class/member marked with @Keep requires special handling, without having to remember to consult the ProGuard configuration and risking breaking something. Also most code refactorings in the IDE should retain the @Keep annotation with the class automatically.

来源:

https://medium.com/androiddevelopers/practical-proguard-rules-examples-5640a3907dc9

7 Pro-tips for Room

1. Pre-populate the database

Do you need to add default data to your database, right after it was created or when the database is opened? Use RoomDatabase#Callback! Call the addCallback method when building your RoomDatabase and override either onCreate or onOpen.

onCreate will be called when the database is created for the first time, after the tables have been created. onOpen is called when the database was opened. Since the DAOs can only be accessed once these methods return, we‘re creating a new thread where we’re getting a reference to the database, get the DAO, and then insert the data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Room.databaseBuilder(context.applicationContext,
DataDatabase::class.java, "Sample.db")
// prepopulate the database after onCreate was called
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// moving to a new thread
ioThread {
getInstance(context).dataDao()
.insert(PREPOPULATE_DATA)
}
}
})
.build()

See a full example here.

Note: When using the ioThread approach, if your app crashes at the first launch, in between database creation and insert, the data will never be inserted.

2. Use DAO’s inheritance capability

Do you have multiple tables in your database and find yourself copying the same Insert, Update and Delete methods? DAOs support inheritance, so create aBaseDaoclass, and define your generic @Insert, @Update and @Delete methods there. Have each DAO extend the BaseDao and add methods specific to each of them.

1
2
3
4
5
6
7
8
9
interface BaseDao<T> {
@Insert
fun insert(vararg obj: T)
}
@Dao
abstract class DataDao : BaseDao<Data>() {
@Query("SELECT * FROM Data")
abstract fun getData(): List<Data>
}

See more details here.

The DAOs have to be interfaces or abstract classes because Room generates their implementation at compile time, including the methods from BaseDao.

3. Execute queries in transactions with minimal boilerplate code

Annotating a method with @Transaction makes sure that all database operations you’re executing in that method will be run inside one transaction. The transaction will fail when an exception is thrown in the method body.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Dao
abstract class UserDao {

@Transaction
open fun updateData(users: List<User>) {
deleteAllUsers()
insertAll(users)
}
@Insert
abstract fun insertAll(users: List<User>)
@Query("DELETE FROM Users")
abstract fun deleteAllUsers()
}

You might want to use the @Transaction annotation for @Query methods that have a select statement, in the following cases:

  • When the result of the query is fairly big. By querying the database in one transaction, you ensure that if the query result doesn’t fit in a single cursor window, it doesn’t get corrupted due to changes in the database between cursor window swaps.
  • When the result of the query is a POJO with @Relation fields. The fields are queries separately so running them in a single transaction will guarantee consistent results between queries.

@Delete, @Update and @Insert methods that have multiple parameters are automatically run inside a transaction.

4. Read only what you need

When you’re querying the database, do you use all the fields you return in your query? Take care of the amount of memory used by your app and load only the subset of fields you will end up using. This will also improve the speed of your queries by reducing the IO cost. Room will do the mapping between the columns and the object for you.

Consider this complex User object:

1
2
3
4
5
6
7
8
9
@Entity(tableName = "users")
data class User(@PrimaryKey
val id: String,
val userName: String,
val firstName: String,
val lastName: String,
val email: String,
val dateOfBirth: Date,
val registrationDate: Date)

On some screens we don’t need to display all of this information. So instead, we can create a UserMinimal object that holds only the data needed.

1
2
3
data class UserMinimal(val userId: String,
val firstName: String,
val lastName: String)

In the DAO class, we define the query and select the right columns from the users table.

1
2
3
4
5
@Dao
interface UserDao {
@Query(“SELECT userId, firstName, lastName FROM Users)
fun getUsersMinimal(): List<UserMinimal>
}

5. Enforce constraints between entities with foreign keys

Even though Room doesn’t directly support relations, it allows you to define Foreign Key constraints between entities.

Room has the @ForeignKey annotation, part of the @Entity annotation, to allow using the SQLite foreign key features. It enforces constraints across tables that ensure the relationship is valid when you modify the database. On an entity, define the parent entity to reference, the columns in it and the columns in the current entity.

Consider a User and a Pet class. The Pet has an owner, which is a user id referenced as foreign key.

1
2
3
4
5
6
7
8
@Entity(tableName = "pets",
foreignKeys = arrayOf(
ForeignKey(entity = User::class,
parentColumns = arrayOf("userId"),
childColumns = arrayOf("owner"))))
data class Pet(@PrimaryKey val petId: String,
val name: String,
val owner: String)

Optionally, you can define what action to be taken when the parent entity is deleted or updated in the database. You can choose one of the following: NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT or CASCADE, that have same behaviors as in SQLite.

Note: In Room, SET_DEFAULT works as SET_NULL, as Room does not yet allow setting default values for columns.

6. Simplify one-to-many queries via @Relation

In the previousUser-Pet example, we can say that we have a one-to-many relation: a user can have multiple pets. Let’s say that we want to get a list of users with their pets: List.

1
2
data class UserAndAllPets (val user: User,
val pets: List<Pet> = ArrayList())

To do this manually, we would need to implement 2 queries: one to get the list of all users and another one to get the list of pets based on a user id.

1
2
3
4
@Query(“SELECT * FROM Users”)
public List<User> getUsers();
@Query(“SELECT * FROM Pets where owner = :userId”)
public List<Pet> getPetsForUser(String userId);

We would then iterate through the list of users and query the Pets table.

To make this simpler, Room’s @Relation annotation automatically fetches related entities. @Relation can only be applied to a List or Set of objects. The UserAndAllPets class has to be updated:

1
2
3
4
5
6
7
class UserAndAllPets {
@Embedded
var user: User? = null
@Relation(parentColumn = “userId”,
entityColumn = “owner”)
var pets: List<Pet> = ArrayList()
}

In the DAO, we define a single query and Room will query both the Users and the Pets tables and handle the object mapping.

1
2
3
@Transaction
@Query(“SELECT * FROM Users”)
List<UserAndAllPets> getUsers();

7. Avoid false positive notifications for observable queries

Let’s say that you want to get a user based on the user id in an observable query:

1
2
3
4
5
@Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): LiveData<User>
// or
@Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): Flowable<User>

You’ll get a new emission of the User object whenever that user updates. But you will also get the same object when other changes (deletes, updates or inserts) occur on the Users table that have nothing to do with the User you’re interested in, resulting in false positive notifications. Even more, if your query involves multiple tables, you’ll get a new emission whenever something changed in any of them.

Here’s what’s going on behind the scenes:

  1. SQLite supports triggers that fire whenever a DELETE, UPDATE or INSERT happens in a table.
  2. Room creates an InvalidationTracker that uses Observers that track whenever something has changed in the observed tables.
  3. Both LiveData and Flowable queries rely on the InvalidationTracker.Observer#onInvalidated notification. When this is received, it triggers a re-query.

Room only knows that the table has been modified but doesn’t know why and what has changed. Therefore, after the re-query, the result of the query is emitted by the LiveData or Flowable. Since Room doesn’t hold any data in memory and can’t assume that objects have equals(), it can’t tell whether this is the same data or not.

You need to make sure that your DAO filters emissions and only reacts to distinct objects.

If the observable query is implemented using Flowables, use Flowable#distinctUntilChanged.

1
2
3
4
5
6
7
8
9
10
11
12
@Dao
abstract class UserDao : BaseDao<User>() {
/**
* Get a user by id.
* @return the user from the table with a specific id.
*/
@Query(“SELECT * FROM Users WHERE userid = :id”)
protected abstract fun getUserById(id: String): Flowable<User>
fun getDistinctUserById(id: String):
Flowable<User> = getUserById(id)
.distinctUntilChanged()
}

If your query returns a LiveData, you can use a MediatorLiveData that only allows distinct object emissions from a source.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
val distinctLiveData = MediatorLiveData<T>()
distinctLiveData.addSource(this, object : Observer<T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged(obj: T?) {
if (!initialized) {
initialized = true
lastObj = obj
distinctLiveData.postValue(lastObj)
} else if ((obj == null && lastObj != null)
|| obj != lastObj) {
lastObj = obj
distinctLiveData.postValue(lastObj)
}
}
})
return distinctLiveData
}

In your DAOs, make method that returns the distinct LiveData public and the method that queries the database protected.

1
2
3
4
5
6
7
@Dao
abstract class UserDao : BaseDao<User>() {
@Query(“SELECT * FROM Users WHERE userid = :id”)
protected abstract fun getUserById(id: String): LiveData<User>
fun getDistinctUserById(id: String):
LiveData<User> = getUserById(id).getDistinct()
}

See more of the code here.

Note: if you’re returning a list to be displayed, consider using the Paging Library and returning a LivePagedListBuilder since the library will help with automatically computing the diff between list items and updating your UI.

来源:

https://medium.com/androiddevelopers/7-pro-tips-for-room-fbadea4bfbd1

翻译:

https://baijiahao.baidu.com/s?id=1626702427361302777&wfr=spider&for=pc

LiveData 的 map 与 switchMap 操作

LiveData可以通过Transformations的map和switchMap操作,将一个LiveData转成另一种类型的LiveData,效果与RxJava的map/switchMap操作符类似。

可以看看两个函数的声明

1
2
3
4
5
6
7
8
public static <X, Y> LiveData<Y> map(
@NonNull LiveData<X> source,
@NonNull final Function<X, Y> mapFunction)


public static <X, Y> LiveData<Y> switchMap(
@NonNull LiveData<X> source,
@NonNull final Function<X, LiveData<Y>> switchMapFunction)

根据以上代码,我们可以知道,对应的变换函数返回的类型是不一样的:map是基于泛型类型的变换,而switchMap则返回一个新的LiveData。

来源:

https://mp.weixin.qq.com/s/qzyfKv6iOHlVobVYi-Q4Mw

android 透明色

设置alpha有一种局限性,会使得内容也变得透明,但是使用透明色只会使背景透明,内容不透明

不透明

100%FF

95%F2

90%E6

85%D9

80%CC

75%BF

70%B3

65%A6

60%99

55%8C

半透明50%80

45%73

40%66

35%59

30%4D

25%40

20%33

15%26

10%1A

5%0D

全透明

0%00

举例:

全透明:#00000000

半透明:#80000000

不透明:#FF000000

白色半透明:#80FFFFFF

红色30%透明:#4Dca0d0d

来源:

https://www.jianshu.com/p/abc95c2a0fa9

Bottom Navigation

Disable Transitions Ways:

The above transition we can remove by adding this

1
2
3
<com.google.android.material.bottomnavigation.BottomNavigationView
...
app:itemHorizontalTranslationEnabled="false"/>

Now, we can also show label and remove the translation together without app:itemHorizontalTranslationEnabled="false" this way

1
2
3
<com.google.android.material.bottomnavigation.BottomNavigationView
...
app:labelVisibilityMode="labeled"/>

If we want the same size of bottom navigation text then we can use the value of dimen.xml just add this line.

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<dimen name="design_bottom_navigation_active_text_size"
tools:override="true">12sp</dimen>
</resources>

Problem:

The one problem is still here, What if the menu text is a long text? What if it was made of 2 words?

you will see the long text trimmed when the menu is selected. (Please look at the third menu)

Solution:

You just need to hide the long text and show the small text by doing like this below snippet of code.

1
TextView largeTextView = bottomNavigationView.findViewById(itemID)            .findViewById(com.google.android.material.R.id.largeLabel);    TextView smallTextView = bottomNavigationView.findViewById(itemID)            .findViewById(com.google.android.material.R.id.smallLabel);    smallTextView.setVisibility(View.VISIBLE);    largeTextView.setVisibility(View.GONE);