https://developer.android.com/training/tv/start/
https://developer.android.com/training/tv/playback/

https://designguidelines.withgoogle.com/android-tv/android-tv/introduction.html#

https://developer.nvidia.com/android-tv-deployment-checklist

http://corochann.com/android-tv-application-hands-on-tutorial-1-45.html
https://github.com/corochann/AndroidTVappTutorial

tvheadend

web: https://tvheadend.org/
github: https://github.com/tvheadend/tvheadend
tv: https://www.youtube.com/watch?v=rDQyHFZ-l9Y
android: https://github.com/rsiebert/TVHClient

tool

https://android-developers.googleblog.com/2016/11/adding-tv-channels-to-your-app-with-the-tif-companion-library.html

Only after understanding the surrounding code can you make the necessary
modifications.

Note that omitting the return type is allowed only for functions with an expression
body

Note that this example shows the
only place in the Kotlin syntax where you’re required to use semicolons: if you define
any methods in the enum class, the semicolon separates the enum constant list from the
method definitions.

The rule “the last expression in a block is the result” holds in all cases where a block
can be used and a result is expected. As you’ll see at the end of this chapter, the same
rule works for the try body and catch clauses, and chapter 5 discusses its application to
lambda expressions. But as we already mentioned in section 2.2.1, this rule doesn’t hold
for regular functions. A function can have either an expression body that can’t be a block,
or a block body with explicit return statements inside.
p41

Just like many other modern JVM languages, Kotlin doesn’t differentiate between
checked and unchecked exceptions. You don’t specify the exceptions thrown by a
function, and you may or may not handle any exceptions. This design decision is based
on the practice of using checked exceptions in Java. Experience has shown that the Java
rules often require a lot of meaningless code to rethrow or ignore exceptions, and the
rules don’t consistently protect you from the errors that can happen.
p48

Note that extension
functions don’t allow you to break encapsulation. Unlike methods defined in the class,
extension functions don’t have access to private or protected members of the class.
p61

Method overriding in Kotlin works as usual for member functions, but you can’t override
an extension function.
the function that’s called depends on the static type of the
variable being declared, not on the runtime type of the value stored in that variable.
p64-p65

Note: If the class has a member function with the same signature as an
extension function, the member function always takes precedence.
You should keep this in mind when extending the API of classes: if
you add a member function with the same signature as an
extension function that a client of your class has defined, and they
then recompile their code, it will change its meaning and start
referring to the new member function.
p65
//TODO but how to override CharSequence.split, if the member function always takes precedence ???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A(var c: String) {
fun getDiffC(): String {
return "---$c---"
}

fun getDiffD(): String {
return "!!!!!$c!!!!!"
}
}
fun A.getDiffD(): String = "====${this.c}===="
fun main(args: Array<String>) {
println(A("a").getDiffC())
println(A("d").getDiffD())
}

The destructuring declaration feature isn’t limited to pairs. For example, you can
assign a map entry to two separate variables, key and value , as well.
p69

The to function is an extension function. You can create a pair of any elements,
which means it’s an extension to a generic receiver: you can write 1 to "one" ,
"one" to 1 , list to list.size() , and so one.
Even though the creation of a new map may look like a special construct in Kotlin,
it’s a regular function with a concise syntax

1
2
3
1.to("one)
// ====equivalent====
1 to "one"

p70

local functions and extensions
Kotlin gives you a cleaner solution: you can nest the functions you’ve extracted in the
containing function. This way, you have the structure you need without any extra
syntactic overhead. (meaning fun in fun)
p75

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
fun validate(user: User,
value: String,
fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Cannot save user ${user.id}: $fieldName is empty")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// Save user to the database
}

4

Declaring a class as a data class instructs the compiler to generate several standard methods
for this class. You can also avoid writing delegating methods by hand, because the delegation
pattern is supported natively in Kotlin.
p78

This chapter also describes a new object keyword that declares a class and also
creates an instance of the class. The keyword is used to express singleton objects,
companion objects, and object expressions
p78

Unlike Java, using the override modifier is mandatory in Kotlin
p79

Whereas Java’s classes and methods are open by
default, Kotlin’s are final by default.
If you want to allow the creation of subclasses of a class, you need to mark the class
with the open modifier. In addition, you need to add the open modifier to everyproperty
or method that can be overridden
p82

Note that if you override a member of a base class or interface, the overriding
member will also be open by default. If you want to change this and forbid the subclasses
of your class from overriding your implementation, you can explicitly mark the
overriding member as final.(不像java中的修饰符不能缩小权限,kotlin中可以)
p83

The meaning of access modifiers in a class (p84)

Modifier Corresponding member Comments
final Can’t be overridden Used by default for class members
open Can be overridden Should be specified explicitly
abstract Must be overridden Can be used only in abstract classes; abstract members can’t have an implementation
override Overrides a member in a superclass Overridden member is open by default, if not marked final

Kotlin offers a new visibility modifier, internal, which means
“visible inside a module.” A module is a set of Kotlin files compiled together. It may be
an IntelliJ IDEA module, an Eclipse project, a Maven or Gradle project, or a set of files
compiled with an invocation of the Ant task.
p84

kotlin visibility modifiers (p85)

Modifier Class Member Top-level declaration
public(default) Visible everywhere Visible everywhere
internal Visible in a moudle Visible in a moudle
protected Visible in a subclasses
private Visible in a class Visible in a file

This is a case of a general rule that
requires all types used in the list of base types and type parameters of a class, or the
signature of a method, to be as visible as the class or method itself. This rule ensures that
you always have access to all types you might need to invoke the function or extend a
class.
p85

Note the difference in behavior for the protected modifier in Java and in Kotlin. In
Java, you can access a protected member from the same package, but Kotlin doesn’t
allow that. In Kotlin, visibility rules are simple, and a protected member is only visible
in the class and its subclasses. Also note that extension functions of a class don’t get
access to its private or protected members.
p85

The difference
is that Kotlin nested classes don’t have access to the outer class instance, unless you
specifically request that.
p86

A nested class in Kotlin with no explicit modifiers is the same as a static nested
class in Java. To turn it into an inner class, so that it contains a reference to an outer
class, you use the inner modifier.
p88

Class A declared within another class B In Java In Kotlin
Nested class (doesn’t store a reference to an outer class) static class A class A
Inner class (store a reference to an outer class) class A inner class A
1
2
3
4
5
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}

p88

In Java, as you know, a class can declare one or more constructors. Kotlin is similar, with
one additional change: it makes a distinction between a primary constructor (which is
usually the main, concise way to initialize a class and is declared outside of the class body)
and a secondary constructor (which is declared in the class body). It also allows
you to put additional initialization logic in initializer blocks

1
2
3
4
5
6
// This block of code surrounded by parentheses is called a primary constructor. It serves two
// purposes: specifying constructor parameters and defining properties that are initialized by
// those parameters.
class User(val nickname: String)


p91

If your class has a superclass, the primary constructor also needs to initialize the
superclass. You can do so by providing the superclass constructor parameters after the
superclass reference in the base class list:

1
2
open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }

p93

If you inherit the Button class and don’t provide any constructors, you have to
explicitly invoke the constructor of the superclass even if it doesn’t have any parameters:

1
2
3
open class Button

class RadioButton: Button()

That’s why you need empty parentheses after the name of the superclass. Note the
difference with interfaces: interfaces don’t have constructors, so if you implement an
interface, you never put parentheses after its name in the supertype list.
p93

If you want to ensure that your class can’t be instantiated by other code, you have to
make the constructor private

1
2
3
4
5
6
7
8
class Secretive private constructor() {}

// or
class Secretive {
private constructor()
}

// or use : companion objects

p94

secondary constructor
The below class doesn’t declare a primary constructor (as you can tell because there are no
parentheses after the class name in the class header), but it declares two secondary
constructors. A secondary constructor is introduced using the constructor keyword
You can declare as many secondary constructors as you need.

1
2
3
4
5
6
7
8
9
open class View {
constructor(ctx: Context) {
// some code
}

constructor(ctx: Context, attr: AttributeSet) {
// some code
}
}

If you want to extend this class, you can declare the same constructors:

1
2
3
4
5
6
7
8
9
class MyButton : View {
constructor(ctx: Context) : super(ctx) {
// ...
}

constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
// ...
}
}

p95

If the class has no primary constructor, then each secondary constructor has to
initialize the base class or delegate to another constructor that does so. Thinking in terms
of the previous figures, each secondary constructor must have an outgoing arrow starting
a path that ends at any constructor of the base class.
p96

1
2
3
4
5
6
7
8
9
class User(val name: String) {
var address: String = "unspecified"
set(value: String) {
println("""
Address was changed for $name:
"$field" -> "$value".""".trimIndent())
field = value
}
}

In the body of the setter, you use the special identifier field to access the value of
the backing field. In a getter, you can only read the value; and in a setter, you can both
read and modify it.
In the body of the setter, you use the special identifier field to access the value of
the backing field. In a getter, you can only read the value; and in a setter, you can both
read and modify it.
Note that you can redefine only one of the accessors for a mutable property. The
getter in the previous example is trivial and just returns the field value, so you didn’t
need to redefine it
p99

In Kotlin, == is the default way to compare two objects: it compares their
values by calling equals under the hood. Thus, if equals is overridden in
your class, you can safely compare its instances using == . For reference
comparison, you can use the === operator, which works exactly thesame
as == in Java.
p102

Note that properties that aren’t declared in the primary
constructor don’t take part in the equality checks and hashcode calculation.
p104

1
2
3
4
5
6
7
- You use the field identifier to reference a property backing field from the accessor body.
- Data classes provide compiler-generated equals() , hashCode() , toString() , copy() , and other methods.
- Companion objects (along with package-level functions and properties) replace Java’s static method and field definitions.
- Companion objects, like other objects, can implement interfaces or have extension functions or properties.
- Object expressions are Kotlin’s replacement for Java’s anonymous inner classes, with
added power such as the ability to implement multiple interfaces and to modify the
variables defined in the scope where the object is created.

5

1
{x:Int, y:Int -> x+y}

A lambda expression in Kotlin is always surrounded by curly braces. Note that there
are no parentheses around the arguments. The arrow separates the argument list from the
body of the lambda.
p122

The road of improvement

1
2
3
data class Person(val name: String, val age: Int)
val people = listOf(Person("lisi", 18), Person("wangwu", 15))
people.maxBy({p:Person -> p.age})

In Kotlin, a syntactic convention lets you move a lambda expression out of parentheses
if it’s the last argument in a function call.
In this example, the lambda is the only argument, so it can be placed
after the parentheses:

1
people.maxBy() { p: Person -> p.age }

When the lambda is the only argument to a function, you can also remove the empty
parentheses from the call:

1
2
// If a lambda is the only argument, you’ll definitely want to write it without the parentheses.
people.maxBy { p: Person -> p.age }

As with local variables, if the type of a lambda parameter can be inferred, you don’t
need to specify it explicitly.

1
people.maxBy { p -> p.age }

The last simplification you can make in this example is to replace an argument with
the default argument name: it . This name is generated if the context expects a lambda
with only one argument, and its type can be inferred:

1
peopel.maxBy {it.age}

p125

One important difference between Kotlin and Java is that in Kotlin, you aren’t
restricted to accessing final variables. You can also modify variables from within a
lambda. The next example counts the number of client and server errors in the given set
of response status codes:

1
2
3
4
5
6
7
8
9
10
11
12
fun printProblemCounts(responses: Collection<String>) {
var clientErrors = 0
var serverErrors = 0
responses.forEach {
if (it.startsWith("4")) {
clientErrors++
} else if (it.startsWith("5")) {
serverErrors++
}
}
println("$clientErrors client errors, $serverErrors server errors")
}

Note that, by default, the lifetime of a local variable is constrained by the function in
which the variable is declared. But if it’s captured by the lambda, the code that uses this
variable can be stored and executed later. You may ask how this works. When you
capture a final variable, is value is stored together with the lambda code that uses it. For
non-final variables, the value is enclosed in a special wrapper that lets you change it, and
the reference to the wrapper is stored together with the lambda.
p127

An important caveat is that, if a lambda is used as an event handler or is otherwise
executed asynchronously, the modifications to local variables will occur only when the
lambda is executed. For example, the following code isn’t a correct way to count button
clicks:

1
2
3
4
5
6
fun tryToCountButtonClicks(button: Button): Int {   
var clicks = 0
button.onClick { clicks++ }
return clicks // always return 0, you should store the clicks variable
// in a location that remains accessible outside of the function
}

p128

1
2
3
4
5
6
// smaple1
people.filter { it.age == people.maxBy(Person::age).age } // not work well with performance, because calculate maxAge everytime

// sample2
val maxAge = people.maxBy(Person::age).age // work well, only compulate once
people.filter { it.age == maxAge }

Don’t repeat a calculation if you don’t need to! Simple-looking code using lambda
expressions can sometimes obscure the complexity of the underlying operations, so
always keep in mind what is happening in the code you write.
p133

The entry point for lazy collection operations in Kotlin is the Sequence interface. The
interface represents just that: a sequence of elements that can be enumerated one by one.
Sequence provides only one method, iterator , that you can use to obtain the values
from the sequence.

1
2
3
4
people.asSequence() // the lazy way more efficient than eager way when there are a million data.
.map(Person::name)
.filter { it.startsWith("A") }
.toList()

p138

Note
As a rule, use a sequence whenever you have a chain of
operations on a large collection. In section 8.2, we’ll discuss why
eager operations on regular collections are efficient in Kotlin, in
spite of creating intermediate collections. But if the collection
contains a large number of elements, the intermediate rearranging
of elements costs a lot, so lazy evaluation is preferable.
p139

The order of the operations you perform on a collection can affect performance as
well.
If map goes first, each element is transformed. If you apply filter first,
inappropriate elements are filtered out as soon as possible and aren’t transformed.
p141

SAM: single abstract method p144

In addition to returning values, SAM constructors are used when you need to store a
functional interface instance generated from a lambda in a variable. Suppose you want to
reuse one listener for several buttons, as in the following example (in an Android
application, this code can be a part of the Activity.onCreate method):

1
2
3
4
5
6
7
8
9
10
val listener = OnClickListener { view ->
val text = when (view.id) {
R.id.button1 -> "First button"
R.id.button2 -> "Second button"
else -> "Unknown button"
}
toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)

p147

Note that there’s no this in a lambda as there is in an anonymousobject:
there’s no way to refer to the anonymous class instance into which the
lambda is converted. From the compiler’s point of view, the lambda is a
block of code, not an object, and you can’t refer to it as an object.
The 'this' reference in a lambda refers to a surrounding class.
p148

If your event listener needs to unsubscribe itself while handling an event,
you can’t use a lambda for that. Use an anonymous object to implement
a listener, instead. In an anonymous object, the this keyword refers to
the instance of that object, and you can pass it to the API that removes
the listener.

1
2
3
4
5
6
val  anonymousObject: Runnable = object : Runnable {
override fun run() {
println("world")
}
}
Computation().postponeComputation(1, anonymousObject)

p148

6

To reiterate, a type without a question mark denotes that variables of this type can’t
store null references. This means all regular types are non- null by default, unless
explicitly marked as nullable.
p156

配置域名(需支持泛域名功能)

子域名 记录类型 线路类型 记录值
ngrok A 记录 通用 170.10.10.100
*.ngrok A 记录 通用 170.10.10.100

安装 git

安装并配置好 go

1
2
3
4
5
6
7
sudo add-apt-repository ppa:gophers/archive
sudo apt-get update
sudo apt-get install golang-1.10-go -y

mkdir -p $HOME/c
mkdir -p $HOME/w
ln -sf /usr/lib/go-1.10 $HOME/c/go
1
2
3
export GOROOT=$HOME/c/go
export GOPATH=$HOME/w/go
export PATH=$GOROOT/bin:${GOPATH}/bin:$PATH

编译生成目标文件

创建并进入临时目录:mkdir $HOME/t && cd $_
创建文件build_ngrok.sh加入以下内容

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
#!/bin/sh
read -p "Input your domain name:" DOMAIN
if [ "$DOMAIN" = "" ];then
echo Please input your domain name.
exit 0
fi

resultFileName=ngrok_`echo ${DOMAIN} | sed 's/\./_/g'`

currentPwd=$(pwd)
echo current path: $currentPwd
go get github.com/inconshreveable/ngrok
cd $GOPATH/src/github.com/inconshreveable/ngrok
git clean -df
git checkout -- .

openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$DOMAIN" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key

make release-server
GOOS=linux GOARCH=amd64 make release-client
GOOS=windows GOARCH=amd64 make release-client
GOOS=linux GOARCH=arm make release-client
GOOS=darwin GOARCH=amd64 make release-client

mkdir -p bin/tls
mkdir -p bin/out

cp device.crt bin/tls/snakeoil.crt
cp device.key bin/tls/snakeoil.key
echo 'nohup ./ngrokd -tlsKey="tls/snakeoil.key" -tlsCrt="tls/snakeoil.crt" -domain='"$DOMAIN"' -httpAddr=":80" -httpsAddr=":443" > out/nohupd.out 2>&1 &' > ./bin/start.sh
chmod +x ./bin/start.sh
echo "server_addr: $DOMAIN:4443" > ./bin/ngrok.cfg
echo "trust_host_root_certs: false" >> ./bin/ngrok.cfg
echo 'nohup ./ngrok -config=./ngrok.cfg -subdomain=blog -proto=http 8078 > /dev/null 2>&1 &' > ./bin/blog.sh
chmod +x ./bin/ngrok_blog.sh

mv bin ${resultFileName}
tar -zcvf ${resultFileName}.tar.gz ${resultFileName}
mv ${resultFileName}.tar.gz $currentPwd/${resultFileName}.tar.gz
git clean -df
git checkout -- .
echo ok! result: ${resultFileName}.tar.gz
  • 运行 sh build_ngrok.sh
  • 根据提示输入已经配置好的域名,例如:ngrok.lyloou.com
  • 在域名对应的服务器上运行:./start.sh (这样,服务器端就完成了)

打包和解压

1
2
tar -zcvf ngrok_lyloou_com.tar.gz bin
tar -zxvf ngrok_lyloou_com.tar.gz

下载

realpath ngrok_lyloou_com.tar.gz # 获取文件路径
scp root@170.10.0.100:/root/t/ngrok_lyloou_com.tar.gz ngrok_lyloou_com.tar.gz # 从服务器拉取文件

运行服务器(已经在上面的build_ngrok.sh中配置过了)

1
2
3
4
5
6
7
#!/bin/sh
./ngrokd -domain="ngrok.lyloou.com" -httpAddr=":80" -httpsAddr=":443"

## 或者后台运行
mkdir out
chmod +x ngrokd
nohup ./ngrokd -domain="ngrok.lyloou.com" -httpAddr=":80" -httpsAddr=":443" > out/nohup_log.out 2>&1 &

运行客户端(在上面的build_ngrok.sh中配置并生成了一个案例ngrok_blog.sh

添加配置 ngrok.cfg:

1
2
server_addr: "ngrok.lyloou.com:4443"
trust_host_root_certs: false
1
2
#!/bin/sh
nohup ./ngrok -config=./ngrok.cfg -subdomain=lou -proto=http 80 > /dev/null 2>&1 &

其他

如果在云平台中运行 ngrok 服务, 需要将4443端口80端口添加到安全组中,如下表:

授权策略 协议类型 端口范围 授权类型(全部) 授权对象
允许 自定义 TCP 4443/4443 IPv4 地址段访问 0.0.0.0/0
允许 自定义 TCP 80/80 IPv4 地址段访问 0.0.0.0/0

参考资料

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#一、系统说明

- Windows 使用`windows_amd64`版本
- Mac 使用`darwin_amd64`版本
- Linux 使用`linux`版本

#二、配置说明
.\ngrok.exe -config=.\ngrok.cfg -subdomain=lou -proto=http 80

- subdomain 指向自定义的子域名
- proto 指向使用的协议
- 随后的数字表示:准备映射出去的端口号

# 三、使用方法

- Windows 系统执行 ngrok.bat
- Mac 和 Linux 系统执行 ngrok.sh

# 四、访问方式

浏览器中打开链接:
[http://lou.ngrok.lyloou.com](http://lou.ngrok.lyloou.com)

源码

https://github.com/lyloou/build_ngrok

install

1
2
sudo apt update
sudo apt install redis-server

安装时出现 Errors were encountered while processing: redis-server 的问题

You should probably alter the redis.conf file to force it to use IPv4 if it supports that mode only and then maybe you could run it without IPv6.
apt - Cannot install redis server - Unix & Linux Stack Exchange > https://unix.stackexchange.com/a/501785

单机安装 ubuntu

1
2
3
4
5
6
7
wget -v http://download.redis.io/releases/redis-5.0.5.tar.gz
apt install build-essential tcl
mkdir /var/redis-standalone
make install PREFIX=/var/redis-standalone MALLOC=libc
cp ./redis.conf /var/redis-standalone
cd /var/redis-standalone
./bin/redis-server ./redis.conf

单机安装 centos

1
2
3
4
5
6
7
8
9
yum install wget -y
wget https://download.redis.io/releases/redis-5.0.8.tar.gz
tar -zxf redis-5.0.8.tar.gz
yum install gcc -y
cd redis-5.0.8
make && make install
cd /usr/local/bin/
cp ~/redis-5.0.8/redis.conf .
redis-server redis.conf

参考资料

1
EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*

set password

1
2
3
4
5
sudo vim /etc/redis/redis.conf
# find and uncomment line : # requirepass foobared

# then restart server
systemctl restart redis-server

redis 过期策略及定期策略配置_bobozai86 的博客-CSDN 博客_redis 过期策略 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

redis 过期策略
redis 过期策略是:定期删除+惰性删除。
所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。

但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?
答案是:走内存淘汰机制。

内存淘汰机制
redis 内存淘汰机制有以下几个:
• noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
• allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
• allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
• volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
• volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
• volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

调整 Redis 定期任务的执行频率

docker 搭建 redis 伪集群

参考资料

创建网段

1
2
3
docker network create redis-net
docker network ls
docker inspect redis-net

模板配置 redis-cluster.tmpl

1
2
3
4
5
6
7
8
9
port ${PORT}
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.20.0.1
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
appendonly yes

根据模板批量创建配置文件

1
2
3
4
5
for port in `seq 6000 6005`; do \
mkdir -p ./${port}/conf \
&& PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \
&& mkdir -p ./${port}/data; \
done

批量运行(注意修改目录)

1
2
3
4
5
6
7
for port in `seq 6000 6005`; do \
docker run -d -ti -p ${port}:${port} -p 1${port}:1${port} \
-v /root/w/cluster/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
-v /root/w/cluster/redis-cluster/${port}/data:/data \
--restart always --name redis-${port} --net redis-net \
--sysctl net.core.somaxconn=1024 redis:5.0.3 redis-server /usr/local/etc/redis/redis.conf; \
done

运行和查看

1
2
3
4
5
6
7
docker exec -it redis-6000 bash
redis-cli --cluster create 172.20.0.1:6000 172.20.0.1:6001 172.20.0.1:6002 172.20.0.1:6003 172.20.0.1:6004 172.20.0.1:6005 --cluster-replicas 1

# 连接客户端,查看主从信息
redis-cli -c -p 6000
redis> keys *
redis> info replication

开放防火墙

1
2
3
4
5
6
7
for port in `seq 7000 7005`; do \
firewall-cmd --zone=public --add-port=${port}/tcp --permanent
done

#重新载入
firewall-cmd --reload

停止和移除

1
2
3
4
5
# 暂停容器并删除容器
for port in `seq 6000 6005`; do \
docker stop redis-${port};
docker rm redis-${port};
done

https://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650826588&idx=1&sn=21288ece071c7c0d1ead1d4cd8a95c67&chksm=80b7b3c2b7c03ad4877d1204f27734b7b5f13990d2af774df5b96f699cc28778e43843d007b2&scene=0#rd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1. Android Jetpack 架构组件之 Lifecycle(使用篇)
https://blog.csdn.net/Alexwll/article/details/80638905

2. Android Jetpack 架构组件之 Lifecycle(源码篇)
https://blog.csdn.net/Alexwll/article/details/82491901

3. Android Jetpack 架构组件之 ViewModel (源码篇)
https://blog.csdn.net/Alexwll/article/details/82459614

4. Android Jetpack 架构组件之 LiveData(使用、源码篇)
https://blog.csdn.net/Alexwll/article/details/82996003

5. Android Jetpack架构组件之 Paging(使用、源码篇)
https://blog.csdn.net/Alexwll/article/details/83246201

6. Android Jetpack 架构组件之 Room(使用、源码篇)
https://blog.csdn.net/Alexwll/article/details/83033460

7. Android Jetpack 架构组件之Navigation
https://blog.csdn.net/Alexwll/article/details/83244004

8. Android Jetpack架构组件之WorkManger
https://blog.csdn.net/Alexwll/article/details/83244871

即学即用 Android Jetpack - ViewModel & LiveData - 简书

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

可以通过下面的图来看 ViewModelLiveData 之间的关系

还可以通过下面的代码来看 ViewModelLiveData 之间的关系

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
class ShoeModel constructor(shoeRepository: ShoeRepository) : ViewModel() {

// 品牌的观察对象 默认观察所有的品牌
private val brand = MutableLiveData<String>().apply {
value = ALL
}

// 鞋子集合的观察类
val shoes: LiveData<List<Shoe>> = brand.switchMap {
// Room数据库查询,只要知道返回的是LiveData<List<Shoe>>即可
if (it == ALL) {
shoeRepository.getAllShoes()
} else {
shoeRepository.getShoesByBrand(it)
}
}

fun setBrand(brand:String){
this.brand.value = brand

this.brand.map {

}
}

fun clearBrand(){
this.brand.value = ALL
}

companion object {
private const val ALL = "所有"
}
}

验证 LiveData 的在两个 fragments 上共享数据

源码

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
// LoginLiveData
class LoginLiveData : MutableLiveData<LoginInfo>() {

companion object {
private lateinit var sInstance: LoginLiveData

@MainThread
fun get(): LoginLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else LoginLiveData()
return sInstance
}
}
}

// LoginFragment.onViewCreated
Log.e("TTAG", "from LoginFragment, the init liveData is: ${LoginLiveData.get().value}")
LoginLiveData.get().observe(viewLifecycleOwner, Observer {
Log.e("TTAG", "from LoginFragment, the liveData changed: $it")
})
Handler().postDelayed({
LoginLiveData.get().apply {
value = LoginInfo("login", "login", "login@qq.com")
}
}, 2000)

// WelcomeFragment.onViewCreated
Log.e("TTAG", "from LoginFragment, the init liveData is: ${LoginLiveData.get().value}")
LoginLiveData.get().observe(viewLifecycleOwner, Observer {
Log.e("TTAG", "from LoginFragment, the liveData changed: $it")
})
Handler().postDelayed({
LoginLiveData.get().apply {
value = LoginInfo("login", "login", "login@qq.com")
}
}, 2000)

日志

1
2
3
4
5
6
7
# 首次进入welcome
E/TTAG: from WelcomeFragment, the init liveData is: null
E/TTAG: from WelcomeFragment, the liveData changed:LoginInfo(account=welcome, pwd=welcome, email=welcome@qq.com)
# 点击进入login
E/TTAG: from LoginFragment, the init liveData is: LoginInfo(account=welcome, pwd=welcome, email=welcome@qq.com)
E/TTAG: from LoginFragment, the liveData changed: LoginInfo(account=welcome, pwd=welcome, email=welcome@qq.com)
E/TTAG: from LoginFragment, the liveData changed: LoginInfo(account=login, pwd=login, email=login@qq.com)

Room

即学即用 Android Jetpack - Room - 简书

Android Room 框架学习 - 简书

Paging

问题: Jetpack Paging 闪烁

描述: 一点点的不相关的修改都会导致列表刷新,源码如下

1
2
3
4
5
6
7
8
9
10
companion object {
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<DbFlowDay>() {
override fun areItemsTheSame(oldItem: DbFlowDay, newItem: DbFlowDay): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: DbFlowDay, newItem: DbFlowDay): Boolean {
return oldItem.day == newItem.day
}
}
}

解决,如下源码:

1
2
3
4
5
6
7
8
9
10
companion object {
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<DbFlowDay>() {
override fun areItemsTheSame(oldItem: DbFlowDay, newItem: DbFlowDay): Boolean {
return oldItem.id == newItem.id // !!! 这里,比较id,而不是对象
}
override fun areContentsTheSame(oldItem: DbFlowDay, newItem: DbFlowDay): Boolean {
return oldItem.day == newItem.day
}
}
}

databinding recyclerView

DataBinding 在 RecyclerView 中的使用 - 简书

常用的 lombok 注解

  • @EqualsAndHashCode :实现 equals()方法和 hashCode()方法 @ToString:实现` toString()方法
  • @Data :注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了 equals、canEqual、hashCode、toString 方法
  • @Setter :注解在属性上;为属性提供 setting 方法
  • @Getter :注解在属性上;为属性提供 getting 方法
  • @Log4j :注解在类上;为类提供一个 属性名为 log 的 log4j 日志对象
  • @NoArgsConstructor :注解在类上;为类提供一个无参的构造方法
  • @AllArgsConstructor :注解在类上;为类提供一个全参的构造方法
  • @Cleanup :关闭流 @Synchronized:对象同步 @SneakyThrows:抛出异常

转换数据库数据到实体对象出错

org.springframework.dao.DataIntegrityViolationException: Error attempting to get column ‘content’ from result set. Cause: java.sql.SQLDataException: Cannot convert string ‘2202’ to java.sql.Timestamp value

实际上是因为没有空构造函数引起的错误,参考 改变实体类成员变量的顺序,导致报错 · Issue #1074 · baomidou/mybatis-plus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
@Builder
@NoArgsConstructor // 添加这个
@AllArgsConstructor // 也加上这个
public class Event {
private Long id;

private Date gmtCreate;

private Date gmtModified;

private String day;

private String content;
}

Lombok IDEA 异常

intellij idea - java: You aren’t using a compiler supported by lombok, so lombok will not work and has been disabled - Stack Overflow

-Djps.track.ap.dependencies=false
Build, Execution, Deployment -> Compiler -> Shared build process VM options

0%