-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
333 lines (333 loc) · 381 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[C++(一)]]></title>
<url>%2F2018%2F04%2F08%2FCPP-%E4%B8%80%2F</url>
<content type="text"><![CDATA[简介 C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点C++ 是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。C++ 进一步扩充和完善C,是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序注意:使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查2014 ISO/IEC 14882:2014 C++14 第四个C++标准 C++ 完全支持面向对象的程序设计,包括面向对象开发的四大特性:封装抽象继承多态 标准库标准的 C++ 由三个重要部分组成:核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。C++ 标准库,提供了大量的函数,用于操作文件、字符串等。标准模板库(STL),提供了大量的方法,用于操作数据结构等 MinGW 语法 程序结构 12345678910#include <iostream>using namespace std; // main() 是程序开始执行的地方 ,单行注释 int main(){ cout << "Hello World"; // 输出 Hello World return 0;} 包含头文件<iostream> using namespace std;使用std命名空间 int main()主函数,程序从这里开始执行 return 0 终止main()函数,返回0 编译执行1234$ touch hello.cpp$ g++ hello.cpp$ ./a.exeHello World 分号&快 123456x = y; y = y+1; add(x, y);{ cout << "Hello World"; // 输出 Hello World return 0;} 标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母,数字,下划线,不允许出现标点字符,比如 @、& 和 %,区分大小写的编程语言 关键字 注释 1234567891011/* 用于输出 Hello World 的注释*/cout << "Hello World"; // 输出 Hello World#if 0 ... #endif 来实现注释,且可以实现嵌套,格式为:#if 0 code#endif 你可以把 #if 0 改成 #if 1 来执行 code 的代码,测试时使用 #if 1 来执行测试代码,发布后使用 #if 0 来屏蔽测试代码#if 后可以是任意的条件语句 C++中”\n”与endl的区别 “\n” 表示内容为一个回车符的字符串。std::endl 是流操作子,输出的作用和输出 “\n” 类似,但可能略有区别。std::endl 输出一个换行符,并立即刷新缓冲区。例如:std::cout << std::endl;相当于:std::cout << ‘\n’ << std::flush;或者std::cout << ‘\n’; std::fflush(stdout);由于流操作符 << 的重载,对于 ‘\n’ 和 “\n”,输出效果相同。对于有输出缓冲的流(例如cout、clog),如果不手动进行缓冲区刷新操作,将在缓冲区满后自动刷新输出。不过对于 cout 来说(相对于文件输出流等),缓冲一般体现得并不明显。但是必要情况下使用 endl 代替 ‘\n’ 一般是个好习惯。对于无缓冲的流(例如标准错误输出流cerr),刷新是不必要的,可以直接使用 ‘\n 数据类型 123cout << "bool: \t\t" << "所占字节数:" << sizeof(bool); cout << "\t最大值:" << (numeric_limits<bool>::max)(); cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl; 它是一种 整型 类型,里面保存的是一个整数,就像 int, long 那样。这种整数用来记录一个大小(size)。size_t 的全称应该是 size type,就是说一种用来记录大小的数据类型,通常我们用 sizeof(XXX) 操作,这个操作所得到的结果就是 size_t 类型,因为 size_t 类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为 int 并赋值给 int 类型的变量,类似的还有 wchar_t, ptrdiff_t,wchar_t 就是 wide char type,一种用来记录一个宽字符的数据类型ptrdiff_t 就是 pointer difference type, 一种用来记录两个指针之间的距离的数据类型通常,size_t 和 ptrdiff_t 都是用 typedef 来实现的。你可能在某个头文件里面找到类似的语句通常我们用 sizeof(XXX) 操作,这个操作所得到的结果就是 size_t 类型因为 size_t 类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为 int 并赋值给 int 类型的变量类似的还有 wchar_t, ptrdiff_twchar_t 就是 wide char type, 一种用来记录一个宽字符的数据类型ptrdiff_t 就是 pointer difference type, 一种用来记录两个指针之间的距离的数据类型通常,size_t 和 ptrdiff_t 都是用 typedef 来实现的。你可能在某个头文件里面找到类似的语句:typedef unsigned int size_t;而 wchar_t 则稍有不同。在一些旧的编译器中,wchar_t 也可能是用 typedef 来实现,但是新的标准中 wchar_t 已经是 C/C++ 语言的关键字,wchar_t 类型的地位已经和 char, int 的地位等同了。在标准 C/C++ 的语法中,只有 int float char bool 等基本的数据类型,至于 size_t, 或 size_type 都是以后的编程人员为了方便记忆所定义的一些便于理解的由基本数据类型的变体类型12345678910111213141516171819typedef unsigned int size_t;int i; // 定义一个 int 类型的变量 isize_t size=sizeof(i); // 用 sizeof 操作得到变量i的类型的大小// 这是一个size_t类型的值// 可以用来对一个size_t类型的变量做初始化i=(int)size; // size_t 类型的值可以转化为 int 类型的值char c='a'; // c 保存了字符 a,占一个字节wchar_t wc=L'a'; // wc 保存了宽字符 a,占两个字节// 注意 'a' 表示字符 a,L'a' 表示宽字符 aint arr[]={1,2,3,4,5}; // 定义一个数组int *p1=&arr[0]; // 取得数组中元素的地址,赋值给指针int *p2=&arr[3];ptrdiff_t diff=p2-p1; // 指针的减法可以计算两个指针之间相隔的元素个数// 所得结果是一个 ptrdiff_t 类型i=(int)diff; // ptrdiff_t 类型的值可以转化为 int 类型的值 typedef 声明,定义已有类型别名1typedef int feet; typedef 可以声明各种类型名,但不能用来定义变量。用 typedef 可以声明数组类型、字符串类型,使用比较方便。用typedef只是对已经存在的类型增加一个类型名,而没有创造新的类型。当在不同源文件中用到同一类型数据(尤其是像数组、指针、结构体、共用体等类型数据)时,常用 typedef 声明一些数据类型,把它们单独放在一个头文件中,然后在需要用到它们的文件中用 #include 命令把它们包含进来,以提高编程效率。使用 typedef 有利于程序的通用与移植。有时程序会依赖于硬件特性,用 typedef 便于移植 枚举123456789101112enum 枚举名{ 标识符[=整型常数], 标识符[=整型常数], ... 标识符[=整型常数]} 枚举变量;enum color { red, green, blue } c;c = blue;enum color { red, green=5, blue };red值为0,green值为5,blue值为6 变量 变量类型 123456float f, salary;double d;extern int d = 3, f = 5; // d 和 f 的声明 int d = 3, f = 5; // 定义并初始化 d 和 fbyte z = 22; // 定义并初始化 zchar x = 'x'; // 变量 x 的值为 'x' 变量声明 123456789101112131415161718192021222324252627282930313233343536373839404142#include <iostream>using namespace std; // 变量声明extern int a, b;extern int c;extern float f; int main (){ // 变量定义 int a, b; int c; float f; // 实际初始化 a = 10; b = 20; c = a + b; cout << c << endl ; f = 70.0/3.0; cout << f << endl ; return 0;}// 函数声明int func(); int main(){ // 函数调用 int i = func();} // 函数定义int func(){ return 0;} 左值lvalue 右值Rvalues左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边 1234有效表达式int g = 20;但是下面这个就不是一个有效的语句,会生成编译时错误:10 = 20; 变量的类型间是可以互相转换的,转换又分为自动转换和强制转换自动转换规则:1、若参与运算量的类型不同,则先转换成同一类型,然后进行运算。2、转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。 a、若两种类型的字节数不同,转换成字节数高的类型 b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型3、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。4、char型和short型参与运算时,必须先转换成int型。5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度:1234567int a=1;double b=2.5;a=b;cout << a; //输出为 2,丢失小数部分int a = 1;double b = 2.1;cout << "a + b = " << a + b << endl; //输出为a + b = 3.1 强制转换规则:强制类型转换是通过类型转换运算来实现的。其一般形式为:(类型说明符)(表达式)其功能是把表达式的运算结果强制转换成类型说明符所表示的类型123int a = 1;double b = 2.1;cout << "a + b = " << a + (int)b << endl; //输出为a + b = 3 局部变量,全局变量局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值123456789101112131415#include <iostream>using namespace std; // 全局变量声明int g = 20; int main (){ // 局部变量声明 int g = 10; cout << g; return 0;} 定义全局变量时,系统会自动初始化为下列值12345int 0char '\0'float 0double 0pointer NULL 全局变量从定义处开始至程序结束起作用,即全局变量存在有效作用域12345678include<iostream>using namespace std;int main(){ cout<<"a= "<<a<<endl; //编译不通过,a是未知字符 return 0;}int a=10; //全局变量从此处定义 若要想让 main 函数也使用全局变量 a,可以用 extern 对全局变量进行声明,就可以合法使用了。12345678910include<iostream>using namespace std;int main(){ extern int a; cout<<"a= "<<a<<endl; //合法,输出10 return 0;}int a=10; //全局变量从此处定义 常量 整数常量 123456785 // 十进制0213 // 八进制 0x4b // 十六进制 30 // 整数 30u // 无符号整数 30l // 长整数 30ul // 无符号长整数, U 和 L 的顺序任意 浮点常量 123453.14159 // 合法的314159E-5L // 合法的510E // 非法的:不完整的指数210f // 非法的:没有小数或指数.e55 // 非法的:缺少整数或分数 布尔常量 12true 值代表真false 值代表假 字符常量 定义常量,不能修改赋值,定义时初始化使用 #define 预处理器使用 const 关键字 123456789#define LENGTH 10 #define WIDTH 5#define NEWLINE '\n'const int LENGTH = 10;const int WIDTH = 5;area = LENGTH * WIDTH;cout << area;cout << NEWLINE; 宏定义 #define 和常量 const 的区别类型和安全检查不同宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查编译器处理不同宏定义是一个”编译时”概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期;const常量是一个”运行时”概念,在程序运行使用,类似于一个只读行数据存储方式不同宏定义是直接替换,不会分配内存,存储与程序的代码段中;const常量需要进行内存分配,存储与程序的数据段中定义域不同 12345678910void f1 (){ #define N 12 const int n 12; }void f2 (){ cout<<N <<endl; //正确,N已经定义过,不受定义域限制 cout<<n <<endl; //错误,n定义域只在f1函数中} 定义后能否取消宏定义可以通过#undef来使之前的宏定义失效const常量定义后将在定义域内永久有效12345678void f1(){ #define N 12 const int n = 12; #undef N //取消宏定义后,即使在f1函数中,N也无效了 #define N 21//取消后可以重新定义} 宏定义不能作为参数传递给函数const常量可以在函数的参数列表中出现 修饰符 修饰符 signed、unsigned、long 和 short 可应用于整型,signed 和 unsigned 可应用于字符型,long 可应用于双精度型 1234567891011121314int main(){ short int i; // 有符号短整数 short unsigned int j; // 无符号短整数 j = 50000; i = j; cout << i << " " << j; return 0;}-15536 50000 const 类型的对象在程序执行期间不能被修改改变 volatile 修饰符 volatile 告诉编译器,变量的值可能以程序未明确指定的方式被改变 restrict 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict explicit构造函数是用来防止隐式转换的1234567891011121314151617181920212223242526272829class Test1{public: Test1(int n) { num=n; }//普通构造函数private: int num;};class Test2{public: explicit Test2(int n) { num=n; }//explicit(显式)构造函数private: int num;};int main(){ Test1 t1=12;//隐式调用其构造函数,成功 Test2 t2=12;//编译错误,不能隐式调用其构造函数 Test2 t2(12);//显式调用成功 return 0;}普通构造函数能够被隐式调用。而explicit构造函数只能被显式调用 存储类存储类定义程序中变量/函数的范围(可见性)和生命周期 register 定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置),寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制 static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享 1234567891011121314151617181920212223242526272829303132#include <iostream>// 函数声明 void func(void);static int count = 10; /* 全局变量 */int main(){ while(count--) { func(); } return 0;}// 函数定义void func( void ){ static int i = 5; // 局部静态变量 i++; std::cout << "变量 i 为 " << i ; std::cout << " , 变量 count 为 " << count << std::endl;}变量 i 为 6 , 变量 count 为 9变量 i 为 7 , 变量 count 为 8变量 i 为 8 , 变量 count 为 7变量 i 为 9 , 变量 count 为 6变量 i 为 10 , 变量 count 为 5变量 i 为 11 , 变量 count 为 4变量 i 为 12 , 变量 count 为 3变量 i 为 13 , 变量 count 为 2变量 i 为 14 , 变量 count 为 1变量 i 为 15 , 变量 count 为 0 std standard标准的缩写,标准库函数使用的命名空间,cin 用于从控制台获取用户输入,cout 用于将数据输出到控制台。cin 是输入流对象,cout 是输出流对象,它们分别可以用 >> 和 <<,是因为分别在其类中对相应运算符进行了重载,头文件 <iostream> 在这个命名空间内声明了 istream 与 ostream 等 IO 类,同时在 std 内声明了 istream cin; 与 ostream cout; 静态局部变量有以下特点: 该变量在全局数据区分配内存; 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化; 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0; 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束 extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示: 第一个文件:main.cpp 1234567891011实例#include <iostream> int count ;extern void write_extern(); int main(){ count = 5; write_extern();} 第二个文件:support.cpp 123456789实例#include <iostream> extern int count; void write_extern(void){ std::cout << "Count is " << count << std::endl;} 在这里,第二个文件中的 extern 关键字用于声明已经在第一个文件 main.cpp 中定义的 count。编译这两个文件 12345$ g++ main.cpp support.cpp -o write这会产生 write 可执行程序,尝试执行 write,它会产生下列结果:$ ./writeCount is 5 thread_local 使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。thread_local 说明符可以与 static 或 extern 合并。可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义 1234567891011thread_local int x; // 命名空间下的全局变量class X{ static thread_local std::string s; // 类的static成员变量};static thread_local std::string X::s; // X::s 是需要定义的 void foo(){ thread_local std::vector<int> v; // 本地变量} 运算符 算术运算符 + - * / % ++ -- 关系运算符 == != > < >= <= 逻辑运算符 && || ! 位运算符 赋值运算符,先运算再赋值 杂项运算符 1234567sizeof 返回变量的大小Condition ? X : Y 条件运算符,Condition 为真 ? 则值为 X : 否则值为 Y, 逗号运算符,var = (count=19, incr=10, count+1); 赋值以最后一个表达式运算的结果,var=20. 和 -> 成员运算符,引用类,结构,共用体的成员Cast 强制转换运算符& 指针运算符,返回变量的地址,&a 给出变量的实际地址* 指针运算符,指向一个变量,*var 指向变量var 运算符优先级,由高到低 if(i<j<k) 和 if(i<j && j<k)第一个i<j或者为0或者为1,只要k大于1,表达式就为true第二个必须i<j且j<k表达式才为true区分 if(val) 和 if(val == true)第一个只要val非零则表达式为true,val为0则表达式为false第二个只有val为1表达式为true,val非1则表达式为false 1234int val = 2;if(val==true){ //不会进入if cout<<"val==true"<<endl;} 多个赋值操作符中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。1234int ival; int *pval;ival = pval = 0; //error 尽管ival和pval都可以赋值为0string s1,s2;s1 = s2 = "OK" //ok 如果指针指向不是用new分配的内存地址,则在该指针上使用delete是不合法的通常编译器不能断定一个指针是否指向动态对象,因此尽管这样做是错误的,但在大部分编译器上仍能运行通过,但是会产生运行时错误。整形提升对于所有比int小的整形(char, signed char, unsigned char, short, unsigned short),如过该类型所有可能值都包含在int中,他们会被提升为int型,否则,他们将被提升为unsigned int对于包含signed和unsigned int型的表达式,表达式中的signed型整数会被转换为unsigned型123456int i = -5;unsigned int ii = 1;cout << (i > ii) << endl; //输出1,原因是int型的i转换为unsigned int型short s = -5;unsigned short ss = 1;cout << (s > ss) << endl; //输出0 比较时short和unsigned short都提升为int型 循环 while for do...while 嵌套循环 break 终止;continue 跳过主体剩余代码,重新开始下个条件;goto 跳转到被标记的语句,不建议使用 无限循环,表达式留空,假设为真1234for( ; ; ){ printf("This loop will run forever.\n"); } 判断 if if...else 嵌套if switch 嵌套switch ?: 条件运算符 函数 指针 指针数组 多级指针 指针形参或数组形参 返回值指针123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244#include <iostream>#include <ctime>using namespace std;int max(int, int);void fun_swap_value(int, int);void fun_swap_point(int *, int *);void fun_swap_cite(int, int);void fun_pptr();void ptrArray();void ptrCompare();void getSeconds(unsigned long *par);void getAvg(int *ptr, int size);int *getRandom();int main() { int a = 100;//实际整型变量 int b = 200; char var1[10]; //变量的地址 cout << "a address " << &a << endl; cout << "var1 address " << &var1 << endl; //指针是一个变量,值为变量的地址,下面不同类型的指针的值 //都是代表内存地址的十六进制数,指针所指向的变量类型不同 int *ip = &a;//定义整型指针,并赋值变量的地址 //输出在指针变量中存储的地址 cout << "ip address " << ip << endl; //访问指针中地址的变量值 cout << "*ip value " << *ip << endl; double *dp;//double型的指针 float *fp;//float型的指针 char *cp;//char型的指针 //为指针变量赋一个 NULL 值是一个良好的编程习惯 //赋为 NULL 值的指针被称为空指针 //NULL 指针是一个定义在标准库中的值为零的常量 //该指针不指向一个可访问的内存位置 int *ptr0 = NULL; cout << "ptr value is " << ptr0 << endl; if (!ptr0) cout << "ptr is NULL" << endl; int var[3] = {10, 100, 200}; int *ptr; ptr = var; //指针中第一个元素的地址,这里不使用&var,指针类型不一致,但是与&var[0]相同 //因为ptr指向的是int *var[0]的地址,而不是int[3] *var //ptr = &var[0];//也可以使用这种方式 cout << "*int[3] &var address " << &var << endl; for (int i = 0; i < 3; ++i) { cout << "index " << i << " address is " << ptr << " value is " << *ptr << endl; //移动到下一个位置 ptr++; //index 0 address is 0x61feec value is 10 //index 1 address is 0x61fef0 value is 100 //index 2 address is 0x61fef4 value is 200 //如果这里指针指向的变量是整型数据 1000,占4个字节,移动到下个整数,当前位置往后移4个字节,地址是1004 //相应的如果这个变量是字符数据,占1个字节,移动到下个字符,当前位置往后移1个字节,地址是1001 } //var++;不正确,是数组第一个元素的常量,不能作为左值 //var[2]=500; *(var + 2) = 500; cout << "---------" << endl; ptrCompare(); cout << "---------" << endl; //指针数组,包含指针的数组 ptrArray(); cout << "---------" << endl; //二级指针,指向指针的指针,多级间接寻址 fun_pptr(); cout << "---------" << endl; int ret = max(a, b); cout << "max :" << ret << endl; cout << "a=" << a << endl; cout << "b=" << b << endl; fun_swap_value(a, b); cout << "fun_swap_value a=" << a << endl; cout << "fun_swap_value b=" << b << endl; /** 调用函数来交换值 * &a 表示指向 a 的指针,即变量 a 的地址 * &b 表示指向 b 的指针,即变量 b 的地址 */ fun_swap_point(&a, &b); cout << "fun_swap_point a=" << a << endl; cout << "fun_swap_point b=" << b << endl; fun_swap_cite(a, b); cout << "fun_swap_cite a=" << a << endl; cout << "fun_swap_cite b=" << b << endl; cout << "---------" << endl; /**传递指针给函数,声明函数参数为指针类型*/ unsigned long sec; getSeconds(&sec); // 输出实际值 cout << "Number of seconds :" << sec << endl; cout << "---------" << endl; int balance[5] = {1000, 2000, 3000, 4000, 6000}; getAvg(balance, 5); cout << "---------" << endl; int *random = getRandom(); for (int j = 0; j < 10; ++j) { cout << "*(random + " << j << ") : "; cout << *(random + j) << endl; } cout << "---------" << endl; LambdaTest t; t.lambda(); cout << "----END-----" << endl; return 0;}void ptrCompare() { int var[3] = {10, 100, 200}; int *ptr; ptr = var; int i = 0; while (ptr <= &var[2]) { cout << "index " << i << " address is " << ptr << " value is " << *ptr << endl; //指向下一个位置 ptr++; i++; }}void ptrArray() { int var[3] = {10, 100, 200}; int *ptrAry[3]; for (int j = 0; j < 3; ++j) { ptrAry[j] = &var[j]; } for (int k = 0; k < 3; ++k) { cout << "Value of ptrAry[" << k << "] = "; cout << *ptrAry[k] << endl; } const char *names[4] = { "Zara Ali", "Hina Ali", "Nuha Ali", "Sara Ali", }; for (int m = 0; m < 4; m++) { cout << "Value of names[" << m << "] = "; cout << names[m] << endl; cout << &names[m] << endl; cout << *names[m] << endl;//指向的是字符串的第一个字符 cout << *names[m] + 1 << endl;//第一个字符+1 转换ASCII值 cout << *(names[m] + 1) << endl;//指向的是字符串的第二个字符 }}/**多级间接寻址*/void fun_pptr() { int var; int *ptr; int **pptr; var = 3000; ptr = &var; pptr = &ptr; cout << "Value of var :" << var << endl; cout << "Value available at *ptr :" << *ptr << endl; cout << "Value available at **pptr :" << **pptr << endl;}void getSeconds(unsigned long *par) { *par = time(NULL);}void getAvg(int *ptr, int size) { int sum = 0; for (int i = 0; i < size; ++i) { sum += ptr[i]; } double avg = double(sum) / size; cout << "avg is " << avg << endl;}/**函数返回指针*/int *getRandom() { static int r[10]; //设置种子 srand((unsigned) time(NULL)); for (int i = 0; i < 10; ++i) { r[i] = rand(); cout << r[i] << endl; } return r;}int max(int a, int b) { return a > b ? a : b;}/** * 传值调用,默认情况下,C++ 使用传值调用来传递参数 * 实际参数的参数值传值给形式参数,调用不会影响实际参数*/void fun_swap_value(int a, int b) { int temp = a; a = b; b = temp;}/**指针调用 * 实际参数的地址传给形式参数,修改形式参数会影响实际参数 */void fun_swap_point(int *a, int *b) { int temp; temp = *a; // 保存地址 x 的值 *a = *b; //把 y 赋值给 x *b = temp; //把 x 赋值给 y}/**引用调用 * 把引用的地址复制给形式参数。在函数内, * 该引用用于访问调用中要用到的实际参数,修改形式参数会影响实际参数*/void fun_swap_cite(int &a, int &b) { int temp; temp = a; a = b; b = temp;} Lambda表达式 C++11 提供了对匿名函数的支持,称为 Lambda 函数 Lambda 表达式本质上与函数声明非常类似。Lambda 表达式具体形式 capture mutable ->return-type{statement} [capture]:捕捉列表。捕捉列表总是出现在 lambda 表达式的开始处。事实上,[] 是 lambda 引出符。编译器根据该引出符判断接下来的代码是否是 lambda 函数。捕捉列表能够捕捉上下文中的变量供 lambda 函数使用。 (parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号 () 一起省略。 mutable:mutable 修饰符。默认情况下,lambda 函数总是一个 const 函数,mutable 可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。 ->return_type:返回类型。用追踪返回类型形式声明函数的返回类型。出于方便,不需要返回值的时候也可以连同符号 -> 一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。 {statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量 在Lambda表达式内可以访问当前作用域的变量,这是Lambda表达式的闭包(Closure)行为。 与JavaScript闭包不同,C++变量传递有传值和传引用的区别。可以通过前面的[]来指定: [] // 沒有定义任何变量。使用未定义变量会引发错误。 [x, &y] // x以传值方式传入(默认),y以引用方式传入。 [&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。 [=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。 [&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。 [=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。 12345678910111213141516171819202122232425262728293031class LambdaTest {public: void lambda() { int global_x = 0; //没有返回值 [&global_x] { ++global_x; }; //返回true,false [](int x, int y) { return x > y; }; //指定返回int [](int x, int y) -> int { int z = x + y; return z + x; }; [this]() { this->someFunc(); }(); [] {};//最简单的lambda表达式 } /**函数调用运算符的重载方法是const属性的 * 有时候,你想改动传值方式捕获的值,那么就要使用mutable*/ void someFunc() { int x = 10; auto add_x = [x](int a) mutable { x *= 2; return a + x; }; // 复制捕捉x cout << add_x(10) << endl; // 输出 30 }}; 数字 数学运算 double cos(double);该函数返回弧度角(double 型)的余弦。double sin(double);该函数返回弧度角(double 型)的正弦。double tan(double);该函数返回弧度角(double 型)的正切。double log(double);该函数返回参数的自然对数。double pow(double, double);假设第一个参数为 x,第二个参数为 y,则该函数返回 x 的 y 次方。double hypot(double, double);该函数返回两个参数的平方总和的平方根,也就是说,参数为一个直角三角形的两个直角边,函数会返回斜边的长度。double sqrt(double);该函数返回参数的平方根。int abs(int);该函数返回整数的绝对值。double fabs(double);该函数返回任意一个十进制数的绝对值。double floor(double);该函数返回一个小于或等于传入参数的最大整数 12345678910111213141516171819202122232425262728293031#include <iostream>#include <cmath>#include <ctime>using namespace std;int main() { short s = 10; int i = -10000; long l = 100000; float f = 230.45; double d = 200.43; cout << sin(d) << endl; cout << abs(i) << endl; cout << log(s) << endl; cout << pow(s, 2) << endl; cout << hypot(3, 4) << endl; cout << floor(d) << endl; cout << sqrt(f) << endl; //生成随机数,设置种子 srand((unsigned) time(NULL)); //生成10个随机数 int r; for (int j = 0; j < 10; j++) { r = rand(); cout << "random " << r << endl; } return 0;} 数组 固定大小的相同类型元素的顺序集合,数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素 123456789101112type arrayName [ arraySize ];double balance[10];double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0}; // 输出数组中每个元素的值 for ( int j = 0; j < 10; j++ ) { cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl; } setw()函数设置显示宽度 多维数组(二维数组) 1234567891011121314151617181920type arrayName [ x ][ y ];int a[3][4] = { {0, 1, 2, 3} , /* 初始化索引号为 0 的行 */ {4, 5, 6, 7} , /* 初始化索引号为 1 的行 */ {8, 9, 10, 11} /* 初始化索引号为 2 的行 */};//去掉内嵌括号int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};// 一个带有 5 行 2 列的数组 int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}}; // 输出数组中每个元素的值 for ( int i = 0; i < 5; i++ ) for ( int j = 0; j < 2; j++ ) { cout << "a[" << i << "][" << j << "]: "; cout << a[i][j]<< endl; } 指向数组的指针 1234567891011121314151617181920// 带有 5 个元素的双精度浮点型数组double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};double *p; p = balance; // 输出数组中每个元素的值cout << "使用指针的数组值 " << endl; for ( int i = 0; i < 5; i++ ){ cout << "*(p + " << i << ") : "; cout << *(p + i) << endl;} cout << "使用 balance 作为地址的数组值 " << endl;for ( int i = 0; i < 5; i++ ){ cout << "*(balance + " << i << ") : "; cout << *(balance + i) << endl;} 传递数组给函数 12345void myFunction(int *param);void myFunction(int param[10]);void myFunction(int param[]); 函数返回数组 12345678910111213141516171819202122232425262728293031// 要生成和返回随机数的函数int * getRandom( ){ static int r[10]; // 设置种子 srand( (unsigned)time( NULL ) ); for (int i = 0; i < 10; ++i) { r[i] = rand(); cout << r[i] << endl; } return r;} // 要调用上面定义函数的主函数int main (){ // 一个指向整数的指针 int *p; p = getRandom(); for ( int i = 0; i < 10; i++ ) { cout << "*(p + " << i << ") : "; cout << *(p + i) << endl; } return 0;} 字符串 以null字符\0终止的一维字符数组组成了字符串,字符数组的大小比字符”Hello”多一个 123char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};char greeting[] = "Hello"; cstring 函数, 运用char[] 12345678910111213141516char str1[11] = "Hello";char str2[11] = "World";char str3[11];int len ; // 复制 str1 到 str3strcpy( str3, str1);cout << "strcpy( str3, str1) : " << str3 << endl; // 连接 str1 和 str2strcat( str1, str2);cout << "strcat( str1, str2): " << str1 << endl;// 连接后,str1 的总长度len = strlen(str1);cout << "strlen(str1) : " << len << endl; c++ 中的String 12345678910111213141516string str1 = "Hello";string str2 = "World";string str3;int len ; // 复制 str1 到 str3str3 = str1;cout << "str3 : " << str3 << endl; // 连接 str1 和 str2str3 = str1 + str2;cout << "str1 + str2 : " << str3 << endl; // 连接后,str3 的总长度len = str3.size();cout << "str3.size() : " << len << endl; 引用 不存在空引用。引用必须连接到一块合法的内存 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象 引用必须在创建时被初始化。指针可以在任何时间被初始化1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374// 函数声明 引用调用函数void swap(int &x, int &y);double &setValues(int i);double vals[] = {10.2, 13.4, 15.6, 23.5, 67.2};int main() { int i = 17; double d = 15.5; int &r = i; double &s = d; cout << "i:" << i << endl; cout << "r:" << r << endl; cout << "d:" << d << endl; cout << "s:" << s << endl; // 局部变量声明 int a = 100; int b = 200; cout << "a:" << a << endl; cout << "b:" << b << endl; /* 调用函数来交换值 */ swap(a, b); cout << " a:" << a << endl; cout << " b:" << b << endl; setValues(1) = 18.6; setValues(3) = 19.3; for (int j = 0; j < 5; ++j) { cout << "j:" << j << "-" << vals[j] << endl; } //r 的值发生改变,但是指向的地址未变 r=a; cout << "r: " << r << endl; cout<<"address a "<<&a<<endl; cout<<"address r "<<&r<<endl; cout<<"address i "<<&i<<endl; return 0;}// 函数定义void swap(int &x, int &y) { int temp; temp = x; /* 保存地址 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 x 赋值给 y */}double &setValues(int i) { return vals[i];// 返回第 i 个元素的引用}//(1)以引用返回函数值,定义函数时需要在函数名前加 &//(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。//引用作为返回值,必须遵守以下规则:// (1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。// (2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak// (3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性//当返回一个引用时,要注意被引用的对象不能超出作用域//所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用int& func() { int q; //return q; // 在编译时发生错误 static int x; return x; // 安全,x 在函数作用域外依然是有效的} 日期&时间 有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。 结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:1234567891011struct tm { int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61 int tm_min; // 分,范围从 0 到 59 int tm_hour; // 小时,范围从 0 到 23 int tm_mday; // 一月中的第几天,范围从 1 到 31 int tm_mon; // 月,范围从 0 到 11 int tm_year; // 自 1900 年起的年数 int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起 int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起 int tm_isdst; // 夏令时} C/C++ 中关于日期和时间的重要函数1 time_t time(time_t time);该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 .1。2 char ctime(const time_t time);该返回一个表示当地时间的字符串指针,字符串形式 day month year hours:minutes:seconds year\n\03 struct tm localtime(const time_t time);该函数返回一个指向表示本地时间的 tm 结构的指针4 clock_t clock(void);该函数返回程序执行起(一般为程序的开头),处理器时钟所使用的时间。如果时间不可用,则返回 .15 char asctime ( const struct tm time );该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,返回形式为:day month date hours:minutes:seconds year\n\06 struct tm gmtime(const time_t time);该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示7 time_t mktime(struct tm time);该函数返回日历时间,相当于 time 所指向结构中存储的时间8 double difftime ( time_t time2, time_t time1 );该函数返回 time1 和 time2 之间相差的秒数9 size_t strftime();该函数可用于格式化日期和时间为指定的格式1234567891011121314151617181920212223242526#include <ctime>using namespace std;int main() { // 基于当前系统的当前日期/时间 time_t now = time(nullptr); cout << "now " << now << endl; // 把 now 转换为字符串形式 char *dt = ctime(&now); cout << "local date/time " << dt << endl; // 把 now 转换为 tm 结构 tm *gmtm = gmtime(&now); dt = asctime(gmtm); cout << "UTC date/time " << dt << endl; tm *ltm = localtime(&now); cout << "Y: " << ltm->tm_year << endl; cout << "M: " << ltm->tm_mon << endl; cout << "D: " << ltm->tm_mday << endl; cout << "Time: " << ltm->tm_hour << ":"; cout << " " << ltm->tm_min << ":"; cout << " " << ltm->tm_sec << endl; return 0;} 输入输出 <iostream> 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流 <iomanip> 该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务 <fstream> 该文件为用户控制的文件处理声明服务12345678910111213char name[50];int age;cout << "your name age :";cin >> name >> age;cout << " name is " << name << ", age is " << age << endl;char str[] = "exec";cerr << "error " << str << endl;wcerr << "error " << str << endl;clog << "error " << str << endl;wclog << "error " << str << endl; 数据结构参考菜鸟教程]]></content>
<categories>
<category>C++</category>
</categories>
</entry>
<entry>
<title><![CDATA[SQLite(三)]]></title>
<url>%2F2018%2F04%2F08%2FSQLite-%E4%B8%89%2F</url>
<content type="text"><![CDATA[SQLite Java]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>SQLite</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Source Insight 使用]]></title>
<url>%2F2018%2F04%2F07%2FSource-Insight-%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[New project Add all close options->document options->在document type中选择C++ Source->在右边的File Filter里加上*.cc *.s *.S文件,重新添加项目文件: project->Add and Remove Project files 1234其他文件类型*.kconfig;kconfig.**.java;*.jav;*.xml;*.json,*.gradle,*.bat,*.pro,*.mk,*.aidl,*.so,*.jpg,*.png,*.svg,*.webp,*.html,*.htm,*.properties,*.jar,*.aar,*.bin,*.* 常用快捷键 12345678910111213141516171819Ctrl+= :Jump to definitionAlt+/ :Look up referenceF3 : search backwardF4 : search forwardF5: go to LineF7 :Look up symbolsF8 :Look up local symbolsF9 :Ident leftF10 :Ident rightAlt+, :Jump backwordAlt+. : Jump forwardShift+F3 : search the word under cusor backwardShift+F4 : search the word under cusor forwardF12 : incremental searchShift+Ctrl+f: search in projectshift+F8 : hilight wordproject window Ctrl+O打开symbol window Alt+F8打开和关闭 快捷键大全 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197退出程序 : Alt+F4重画屏幕 : Ctrl+Alt+Space完成语法 : Ctrl+E复制一行 : Ctrl+K恰好复制该位置右边的该行的字符 : Ctrl+Shift+K复制到剪贴板 : Ctrl+Del剪切一行 : Ctrl+U剪切该位置右边的该行的字符 : Ctrl+;剪切到剪贴板 : Ctrl+Shift+X剪切一个字 : Ctrl+,左边缩进 : F9右边缩进 : F10插入一行 : Ctrl+I插入新行 : Ctrl+Enter加入一行 : Ctrl+J从剪切板粘贴 : Ctrl+Ins粘贴一行 : Ctrl+P重复上一个动作 : Ctrl+Y重新编号 : Ctrl+R重复输入 : Ctrl+替换 : Ctrl+H智能重命名 : Ctrl+'关闭文件 : Ctrl+W关闭所有文件 : Ctrl+Shift+W新建 : Ctrl+N转到下一个文件 : Ctrl+Shift+N打开 : Ctrl+O重新装载文件 : Ctrl+Shift+O另存为 : Ctrl+Shift+S显示文件状态 : Shift+F10激活语法窗口 : Alt+L回到该行的开始 : Home回到选择的开始 : Ctrl+Alt+[到块的下面 : Ctrl+Shift+]到块的上面 : Ctrl+Shift+[书签 : Ctrl+M到文件底部 : Ctrl+End, Ctrl+(KeyPad) End到窗口底部 : (KeyPad) End (小键盘的END)到一行的尾部 : End到选择部分的尾部 : Ctrl+Alt+]到下一个函数 : 小键盘 +上一个函数 : 小键盘 -后退 : Alt+,, Thumb 1 Click后退到索引 : Alt+M向前 : Alt+., Thumb 2 Click转到行 : F5, Ctrl+G转到下一个修改 : Alt+(KeyPad) +转到下一个链接 : Shift+F9, Ctrl+Shift+L回到前一个修改 : Alt+(KeyPad) -跳到连接(就是语法串口列表的地方) : Ctrl+L跳到匹配 : Alt+]下一页 : PgDn, (KeyPad) PgDn上一页 : PgUp, (KeyPad) PgUp向上滚动半屏 : Ctrl+PgDn, Ctrl+(KeyPad) PgDn, (KeyPad) *向下滚动半屏 : Ctrl+PgUp, Ctrl+(KeyPad) PgUp, (KeyPad) /左滚 : Alt+Left向上滚动一行 : Alt+Down向下滚动一行 : Alt+Up右滚 : Alt+Right选择一块 : Ctrl+-选择当前位置的左边一个字符 : Shift+Left选择当前位置右边一个字符 : Shift+Right选择一行 : Shift+F6从当前行其开始向下选择 : Shift+Down从当前行其开始向上选择 : Shift+Up选择上页 : Shift+PgDn, Shift+(KeyPad) PgDn选择下页 : Shift+PgUp, Shift+(KeyPad) PgUp选择句子(直到遇到一个 . 为止) : Shift+F7, Ctrl+.从当前位置选择到文件结束 : Ctrl+Shift+End从当前位置选择到行结束 : Shift+End从当前位置选择到行的开始 : Shift+Home从当前位置选择到文件顶部 : Ctrl+Shift+Home选择一个单词 : Shift+F5选择左边单词 : Ctrl+Shift+Left选择右边单词 : Ctrl+Shift+Right到文件顶部 : Ctrl+Home, Ctrl+(KeyPad) Home到窗口顶部 : (KeyPad) Home到单词左边(也就是到一个单词的开始) : Ctrl+Left到单词右边(到该单词的结束) : Ctrl+Right排列语法窗口(有三种排列方式分别按1,2,3次) : Alt+F7移除文件 : Alt+Shift+R同步文件 : Alt+Shift+S增量搜索(当用Ctrl + F 搜索,然后按F12就会转到下一个匹配) : F12替换文件 : Ctrl+Shift+H向后搜索 : F3在多个文件中搜索 : Ctrl+Shift+F向前搜索 : F4搜索选择的(比如选择了一个单词,shift+F4将搜索下一个) : Shift+F4搜索 : Ctrl+F浏览本地语法(弹出该文件语法列表窗口,如果你光标放到一个变量/函数等,那么列出本文件该变量/函数等的信息) : F8浏览工程语法 : F7, Alt+G跳到基本类型(即跳到原型) : Alt+0跳到定义出(也就是声明) : Ctrl+=, Ctrl+L Click (select), Ctrl+Double L Click检查引用 : Ctrl+/语法信息(弹出该语法的信息) : Alt+/, Ctrl+R Click (select)高亮当前单词 : Shift+F8语法窗口(隐藏/显示语法窗口) : Alt+F8关闭窗口 : Alt+F6, Ctrl+F4最后一个窗口 : Ctrl+Tab, Ctrl+Shift+Tab Source Insight 常用设置和快捷键大全Source Insight 4.0.0093 Patched]]></content>
<categories>
<category>Tools</category>
</categories>
<tags>
<tag>Source Insight</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java 小知识]]></title>
<url>%2F2018%2F04%2F02%2FJava-%E5%B0%8F%E7%9F%A5%E8%AF%86%2F</url>
<content type="text"><![CDATA[transient vs volatile不同之处 volatile和序列化无关,写数据到改修饰的变量,会同步刷新到内存中,访问变量时从内存中读取,与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分,不具备原子性。只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:1.对变量的写操作不依赖于当前值2.该变量没有包含在具有其他变量的不变式中 第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性 如果凑巧两个线程在同一时间使用不一致的值执行 setLower 和 setUpper 的话,则会使范围处于不一致的状态。例如,如果初始状态是 (0, 5),同一时间内,线程 A 调用 setLower(4) 并且线程 B 调用 setUpper(3),显然这两个操作交叉存入的值是不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是 (4, 3) —— 一个无效值 12345678910111213141516171819@NotThreadSafe public class NumberRange { private int lower, upper; public int getLower() { return lower; } public int getUpper() { return upper; } public void setLower(int value) { if (value > upper) throw new IllegalArgumentException(...); lower = value; } public void setUpper(int value) { if (value < lower) throw new IllegalArgumentException(...); upper = value; }} 正确使用volatile模式 状态标志,公共特性是:通常只有一种状态转换;shutdownRequested 标志从 false 转换为 true,然后程序停止 123456789 volatile boolean shutdownRequested;...public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // do stuff }} 其他待续 序列化会排除被transient修饰的变量,反序列化会返回相应的默认值]]></content>
<categories>
<category>Java</category>
</categories>
</entry>
<entry>
<title><![CDATA[Window和WindowManager]]></title>
<url>%2F2018%2F04%2F02%2FWindow%E5%92%8CWindowManager%2F</url>
<content type="text"><![CDATA[Window和WindowManagerWindow表示一个窗口,可以实现在桌面上显示悬浮窗,Window是一个抽象类,具体实现是PhoneWindow。通过WindowManager创建Window,WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService,WindowManager和WindowManagerService交互是一个IPC过程,Window实际是View的直接管理者 123456789101112131415161718<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> val btn = Button(baseContext) btn.text = "button" btn.setOnClickListener { toast("Window Demo") } val layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT) layoutParams.type = LayoutParams.TYPE_SYSTEM_OVERLAY/TYPE_SYSTEM_ERROR/TYPE_SYSTEM_OVERLAY/TYPE_APPLICATION_PANEL //不需要获取焦点,不需要接收各种输入事件 layoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE //window 区域以为的单击事件传递给底层window,当前window区域以内的自己处理,否则其他window无法收到单击事件 .or(LayoutParams.FLAG_NOT_TOUCH_MODAL) //window 显示在锁屏上 .or(LayoutParams.FLAG_SHOW_WHEN_LOCKED) layoutParams.gravity = Gravity.LEFT.or(Gravity.TOP) layoutParams.x = 100 layoutParams.y = 300 windowManager.addView(btn, layoutParams) public interface WindowManager extends ViewManager 12345678910111213141516public interface ViewManager{ /** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);} 添加触摸事件 123456789101112131415161718192021222324252627282930313233val btn = Button(baseContext) btn.isClickable = true btn.text = "button"// btn.setOnClickListener { toast("Window Demo") } val layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT)// layoutParams.type = LayoutParams.TYPE_SYSTEM_OVERLAY layoutParams.type =LayoutParams.TYPE_APPLICATION_PANEL layoutParams.flags = //不需要获取焦点,不需要接收各种输入事件 LayoutParams.FLAG_NOT_FOCUSABLE //window 区域以为的单击事件传递给底层window,当前window区域以内的自己处理,否则其他window无法收到单击事件 .or(LayoutParams.FLAG_NOT_TOUCH_MODAL) //window 显示在锁屏上 .or(LayoutParams.FLAG_SHOW_WHEN_LOCKED) layoutParams.gravity = Gravity.LEFT.or(Gravity.TOP) layoutParams.x = 100 layoutParams.y = 300 btn.setOnTouchListener { v, event -> when (event.action) { MotionEvent.ACTION_MOVE -> { layoutParams.x = event.rawX.toInt() layoutParams.y = event.rawY.toInt() windowManager.updateViewLayout(btn, layoutParams) } else -> { Log.i("touch", "else") } } return@setOnTouchListener false } windowManager.addView(btn, layoutParams) Window内部机制Window是抽象概念,每一个Window对应一个View和ViewRootImpl,以View的形式存在 接口WindowManager实现类WindowManagerImpl12345678910111213141516@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}@Overridepublic void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params);} @Overridepublic void removeView(View view) { mGlobal.removeView(view, false);}]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[SQLite(二)]]></title>
<url>%2F2018%2F04%2F02%2FSQLite-%E4%BA%8C%2F</url>
<content type="text"><![CDATA[数据库操作 创建 1sqlite3 DatabaseName.db 查看数据库列表 1.databases 导出完整的数据库到一个文本文件 1234sqlite3 DatabaseName.db .dump > DatabaseName.sql恢复数据库sqlite3 DatabaseName.db < DatabaseName.sql SQLite 的 ATTACH DATABASE 语句是用来选择一个特定的数据库,使用该命令后,所有的 SQLite 语句将在附加的数据库下执行 如果数据库尚未被创建,将创建一个数据库,如果数据库已存在,则把数据库文件名称与逻辑数据库 ‘Alias-Name’ 绑定在一起 1ATTACH DATABASE 'DatabaseName' As 'Alias-Name'; DETACH DTABASE 分离数据库,上述附件数据库的反操作,DETACH 命令将只断开给定名称的连接,而其余的仍然有效,无法分离 main 或 temp 数据库 1DETACH DATABASE 'Alias-Name'; 创建表 CREATE TABLE创建表 .tables 命令来验证表是否已成功创建 .schema命令得到表的完整信息 删除表 DROP TABLE database_name.table_name; INSERT 添加新的记录,新的数据 12345678INSERT INTO TABLE_NAME [(column1, column2, column3,...columnN)] VALUES (value1, value2, value3,...valueN);省略列名,保证顺序一致INSERT INTO TABLE_NAME VALUES (value1,value2,value3,...valueN);INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)VALUES (6, 'Kim', 22, 'South-Hall', 45000.00 ); 将第二个表数据添加到第一个表 1234INSERT INTO first_table_name [(column1, column2, ... columnN)] SELECT column1, column2, ...columnN FROM second_table_name [WHERE condition]; SELECT 获取表中数据,返回结果集 12345678SELECT column1, column2, columnN FROM table_name;SELECT * FROM table_name;.header on.mode column.width 10, 20, 10 Schema 信息12345678910111213141516171819列出所有在数据库中创建的表SELECT tbl_name FROM sqlite_master WHERE type = 'table';产生以下结果:tbl_name----------COMPANY列出关于 COMPANY 表的完整信息SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = 'COMPANY';假设在 testDB.db 中已经存在唯一的 COMPANY 表,产生以下结果:CREATE TABLE COMPANY( ID INT PRIMARY KEY NOT NULL, NAME TEXT NOT NULL, AGE INT NOT NULL, ADDRESS CHAR(50), SALARY REAL) 运算符 算术运算符 123456789sqlite> select 10+20;30sqlite> .mode linesqlite> select 10-20;10-20 = -10sqlite> select 1/3; 1/3 = 0sqlite> select 12%5; 12%5 = 2 比较运算符 逻辑运算符 1234567891011121314151617WHERE AGE >= 25 AND/OR SALARY >= 65000;WHERE AGE IS NOT NULLWHERE NAME LIKE 'Ki%';WHERE NAME GLOB 'Ki*';WHERE AGE NOT IN ( 25, 27 );WHERE AGE BETWEEN 25 AND 27;SQL 子查询,子查询查找 SALARY > 65000 的带有 AGE 字段的所有记录,后边的 WHERE 子句与 EXISTS 运算符一起使用 SELECT AGE FROM COMPANY WHERE EXISTS (SELECT AGE FROM COMPANY WHERE SALARY > 65000);AGE----------252325SELECT * FROM COMPANY WHERE AGE > (SELECT AGE FROM COMPANY WHERE SALARY > 65000); 位运算符 A=60, B=13 表达式 布尔表达式 1SELECT * FROM COMPANY WHERE SALARY = 10000; 数值表达式 12SELECT COUNT(*) AS "RECORDS" FROM COMPANY; RECORDS = 7 日期表达式 1234select CURRENT_TIMESTAMP;CURRENT_TIMESTAMP-------------------2018-04-07 15:36:11 Where子句1234567SELECT * FROM COMPANY WHERE AGE BETWEEN 25 AND 27;SELECT AGE FROM COMPANY WHERE EXISTS (SELECT AGE FROM COMPANY WHERE SALARY > 65000); SELECT * FROM COMPANY WHERE AGE > (SELECT AGE FROM COMPANY WHERE SALARY > 65000); AND/OR123SELECT * FROM COMPANY WHERE AGE >= 25 AND SALARY >= 65000;SELECT * FROM COMPANY WHERE AGE >= 25 OR SALARY >= 65000; Update123UPDATE COMPANY SET ADDRESS = 'Texas' WHERE ID = 6;UPDATE COMPANY SET ADDRESS = 'Texas', SALARY = 20000.00; Delete12DELETE FROM COMPANY WHERE ID = 7;DELETE FROM COMPANY; Like 通配符 百分号(%)代表零个、一个或多个数字或字符。下划线(_)代表一个单一的数字或字符 12345AGE 以 2 开头的所有记录SELECT * FROM COMPANY WHERE AGE LIKE '2%';ADDRESS 文本里包含一个连字符(-)的所有记录SELECT * FROM COMPANY WHERE ADDRESS LIKE '%-%'; Glob 星号(*)代表零个、一个或多个数字或字符。问号(?)代表一个单一的数字或字符,大小写敏感 1SELECT * FROM COMPANY WHERE AGE GLOB '2*'; Limit 限制返回数据数量 1SELECT * FROM COMPANY LIMIT 6; 特定的偏移开始提取记录 1234567SELECT * FROM COMPANY LIMIT 3 OFFSET 2;ID NAME AGE ADDRESS SALARY---------- ---------- ---------- ---------- ----------3 Teddy 23 Norway 20000.04 Mark 25 Rich-Mond 65000.05 David 27 Texas 85000.0 Order By 按 NAME 和 SALARY 升序降序排序1SELECT * FROM COMPANY ORDER BY NAME, SALARY [ASC|DESC]; Group By 对相同的数据进行分组,在 SELECT 语句中,GROUP BY 子句放在 WHERE 子句之后,放在 ORDER BY 子句之前123456789查找名称,薪资总和数据,以名称分组SELECT NAME, SUM(SALARY) FROM COMPANY GROUP BY NAME;SELECT NAME, SUM(SALARY) FROM COMPANY GROUP BY NAME ORDER BY NAME;NAME SUM(SALARY)---------- -----------Allen 15000.0David 85000.0James 10000.0 Having 过滤分组数据123456789SELECT column1, column2FROM table1, table2WHERE [ conditions ]GROUP BY column1, column2HAVING [ conditions ]ORDER BY column1, column2名称出现2次的数据SELECT * FROM COMPANY GROUP BY name HAVING count(name) > 2; Distinct 获取重复记录中的唯一一次记录123456SELECT DISTINCT column1, column2,.....columnN FROM table_nameWHERE [condition]去掉重复名称SELECT DISTINCT name FROM COMPANY;]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>SQLite</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SQLite(一)]]></title>
<url>%2F2018%2F03%2F30%2FSQLite-%E4%B8%80%2F</url>
<content type="text"><![CDATA[简介SQLite特点:无服务器、零配置、事务性的 SQL 数据库引擎,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接;非常小轻量级,兼容ACID原则,是进程或线程安全的 ACID是一组强调高可靠性的数据库系统设计原则,在软件崩溃甚至是硬件故障的情况下,数据也不会损坏。当你需要依赖兼容ACID原则的业务时,你不必重复造轮子去实现一致性检查和崩溃恢复机制。如果你有额外的安全保证机制,可以调整牺牲掉ACID的一些可靠性换取更高的性能和数据吞吐量A: atomicity (原子性)C: consistency (一致性)I: isolation (隔离性)D: durability (持久性) SQL92 不支持的特性如下RIGHT OUTER JOIN 只实现了 LEFT OUTER JOINFULL OUTER JOIN 只实现了 LEFT OUTER JOINALTER TABLE 支持 RENAME TABLE 和 ALTER TABLE 的 ADD COLUMN variants 命令,不支持 DROP COLUMN、ALTER COLUMN、ADD CONSTRAINTTrigger 支持 FOR EACH ROW 触发器,但不支持 FOR EACH STATEMENT 触发器VIEWs 在 SQLite 中,视图是只读的,不可以在视图上执行 DELETE、INSERT 或 UPDATE 语句GRANT 和 REVOKE 可以应用的唯一的访问权限是底层操作系统的正常文件访问权限 DDL 数据定义语言CREATE 创建一个新的表,一个表的视图,或者数据库中的其他对象ALTER 修改数据库中的某个已有的数据库对象,比如一个表DROP 删除整个表,或者表的视图,或者数据库中的其他对象 DML 数据操作语言INSERT 创建一条记录UPDATE 修改记录DELETE 删除记录 DQL 数据库查询语言SELECT 从一个或多个表中检索某些记录 安装sqlite3,设置PATH,cmd运行sqlite3 SQLite点命令 .help 获取帮助 .show 命令查看 SQLite 命令提示符的默认设置,下面是修改默认设置属性 sqlite>.header onsqlite>.mode columnsqlite>.timer on sqlite_master 主表中保存数据库表的关键信息,并把它命名为 sqlite_master,如要查看表概要1234567 CREATE TABLE sqlite_master ( type text, name text, tbl_name text, rootpage integer, sql text); SQLite语法 不区分大小写,但是有些命令大小写敏感,GLOB和glob在SQLite不同 SQLite3练习 The GLOB operator is similar to LIKE but uses the Unix file globbing syntax for its wildcards. Also, GLOB is case sensitive, unlike LIKEThe glob(X,Y) function is equivalent to the expression “Y GLOB X” 1234567SELECT trackid, nameFROM tracksWHERE name GLOB 'Man*'; 注释sqlite>.help -- This is a single line comment SELECT、INSERT、UPDATE、DELETE、ALTER、DROP 等,所有的语句以分号;结束 SELECT 12SELECT column1, column2....columnNFROM table_name; ANALYZE 12345ANALYZE;orANALYZE database_name;orANALYZE database_name.table_name; AND/OR 123SELECT column1, column2....columnNFROM table_nameWHERE CONDITION-1 {AND|OR} CONDITION-2; ALTER TABLE (RENAME TO) 123ALTER TABLE table_name ADD COLUMN column_def...;ALTER TABLE table_name RENAME TO new_table_name; ATTACH DATABASE 1ATTACH DATABASE 'DatabaseName' As 'Alias-Name'; DETACH DATABASE 1DETACH DATABASE 'Alias-Name'; BEGIN TRANSACTION 123BEGIN;orBEGIN EXCLUSIVE TRANSACTION; BETWEEN 123SELECT column1, column2....columnNFROM table_nameWHERE column_name BETWEEN val-1 AND val-2; CREATE (UNIQUE) INDEX 12345CREATE INDEX index_nameON table_name ( column_name COLLATE NOCASE );CREATE UNIQUE INDEX index_nameON table_name ( column1, column2,...columnN); ROLLBACK: 123ROLLBACK;orROLLBACK TO SAVEPOINT savepoint_name; SAVEPOINT 1SAVEPOINT savepoint_name; CREATE TABLE 12345678CREATE TABLE table_name( column1 datatype, column2 datatype, column3 datatype, ..... columnN datatype, PRIMARY KEY( one or more columns )); CREATE TRIGGER 1234567CREATE TRIGGER database_name.trigger_name BEFORE INSERT ON table_name FOR EACH ROWBEGIN stmt1; stmt2; ....END; CREATE VIEW 12CREATE VIEW database_name.view_name ASSELECT statement....; CREATE VIRTUAL TABLE 123CREATE VIRTUAL TABLE database_name.table_name USING weblog( access.log );orCREATE VIRTUAL TABLE database_name.table_name USING fts3( ); COUNT 123SELECT COUNT(column_name)FROM table_nameWHERE CONDITION; DELETE 12DELETE FROM table_nameWHERE {CONDITION}; DISTINCT 12SELECT DISTINCT column1, column2....columnNFROM table_name; DROP TABLE/INDEX 1DROP TABLE/INDEX database_name.table_name/index_name; DROP VIEW/TRIGGER 1DROP VIEW/TRIGGER view_name/trigger_name; EXISTS 123SELECT column1, column2....columnNFROM table_nameWHERE column_name EXISTS (SELECT * FROM table_name ) GLOB 123SELECT column1, column2....columnNFROM table_nameWHERE column_name GLOB { PATTERN }; GROUP BY 1234SELECT SUM(column_name)FROM table_nameWHERE CONDITIONGROUP BY column_name; HAVING 12345SELECT SUM(column_name)FROM table_nameWHERE CONDITIONGROUP BY column_nameHAVING (arithematic function condition); WHERE 123SELECT column1, column2....columnNFROM table_nameWHERE CONDITION; UPDATE 123UPDATE table_nameSET column1 = value1, column2 = value2....columnN=valueN[ WHERE CONDITION ]; INSERT INTO 12INSERT INTO table_name( column1, column2....columnN)VALUES ( value1, value2....valueN); IN/NOT IN 123SELECT column1, column2....columnNFROM table_nameWHERE column_name IN/NOT IN (val-1, val-2,...val-N); Like 123SELECT column1, column2....columnNFROM table_nameWHERE column_name LIKE { PATTERN }; ORDER BY 1234SELECT column1, column2....columnNFROM table_nameWHERE CONDITIONORDER BY column_name {ASC|DESC}; SQLite数据类型 存储类 NULL 值是一个 NULL 值INTEGER 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中REAL 值是一个浮点值,存储为 8 字节的浮点数字TEXT 值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储BLOB 值是一个 blob 数据,完全根据它的输入存储 亲和类 该字段的数据将会优先采用亲缘类型作为该值的存储方式 TEXT 数值型数据在被插入之前,需要先被转换为文本格式,之后再插入到目标字段中NUMERIC 当文本数据被插入到亲缘性为NUMERIC的字段中时,如果转换操作不会导致数据信息丢失以及完全可逆,那么SQLite就会将该文本数据转换为INTEGER或REAL类型的数据,如果转换失败,SQLite仍会以TEXT方式存储该数据。对于NULL或BLOB类型的新数据,SQLite将不做任何转换,直接以NULL或BLOB的方式存储该数据。需要额外说明的是,对于浮点格式的常量文本,如”30000.0”,如果该值可以转换为INTEGER同时又不会丢失数值信息,那么SQLite就会将其转换为INTEGER的存储方式INTEGER 对于亲缘类型为INTEGER的字段,其规则等同于NUMERIC,唯一差别是在执行CAST表达式时REAL 其规则基本等同于NUMERIC,唯一的差别是不会将”30000.0”这样的文本数据转换为INTEGER存储方式NONE 不做任何的转换,直接以该数据所属的数据类型进行存储 Boolean数据类型 SQLite 没有单独的 Boolean 存储类。相反,布尔值被存储为整数 0(false)和 1(true) Date 与 Time 数据类型 SQLite 没有一个单独的用于存储日期和/或时间的存储类,但 SQLite 能够把日期和时间存储为 TEXT、REAL 或 INTEGER 值,并且可以使用内置的日期和时间函数来自由转换不同格式 TEXT 格式为 “YYYY-MM-DD HH:MM:SS.SSS” 的日期REAL 从公元前 4714 年 11 月 24 日格林尼治时间的正午开始算起的天数INTEGER 从 1970-01-01 00:00:00 UTC 算起的秒数]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>SQLite</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kotlin for Android(七)]]></title>
<url>%2F2018%2F03%2F25%2FKotlin-for-Android-%E4%B8%83%2F</url>
<content type="text"><![CDATA[创建业务逻辑访问数据 从数据库获取数据检查是否存在对应星期的数据如果有,返回UI并且渲染如果没有,请求服务器获取数据结果被保存在数据库中并且返回UI渲染 数据源应该是一个具体的实现,这样就可以被容易地修改,所以增加一些额外的代码,然后把 command 从数据访问中抽象出来听 1234interface ForecastDataSource {fun requestForecastByZipCode(zipCode: Long, date: Long): ForecastList?} 使用数据库的数据源和服务端数据源。顺序是很重要的,因为它会根据顺序去遍历这个sources,然后一旦获取到有效的返回值就会停止查询。逻辑顺序是先在本地查询(本地数据库中),然后再通过API查询 1234567891011121314151617class ForecastProvider(private val source: List<ForecastDataSource> = ForecastProvider.SOURCES) { companion object { val DAY_IN_MILLIS = 1000 * 60 * 60 * 24 val SOURCES = listOf(ForecastDb(), ForecastServer()) } fun requestByZipCode(zipCode: Long, days: Int): ForecastList = source.firstResult { requestSource(it, days, zipCode) } private fun requestSource(source: ForecastDataSource, days: Int, zipCode: Long): ForecastList? { val res = source.requestForecastByZipCode(zipCode, todayTimeSpan()) return if (res != null && res.size >= days) res else null } private fun todayTimeSpan() = System.currentTimeMillis() / DAY_IN_MILLIS * DAY_IN_MILLIS} 该函数接收一个断言函数,它接收一个 T 类型的对象然后返回一个 R? 类型的值。这表示 predicate 可以返回null类型,但是我们的 firstResult 不能返回null 123456789inline fun <T, R : Any> Iterable<T>.firstResult(predicate: (T) -> R?) : R {for (element in this){val result = predicate(element)if (result != null) return result}throw NoSuchElementException("No element matching predicatewas found.")} 请求服务端数据,ForecastDb保存数据到数据库,被重写的方法用来请求服务器,转换结果到 domain objects 并保存它们到数据库。它最后查询数据库返回数据,这是因为我们需要使用到插入到数据库中的字增长id 123456789101112class ForecastServer(private val dataMapper: ServerDataMapper = ServerDataMapper(), private val forecastDb: ForecastDb = ForecastDb()) : ForecastDataSource { override fun requestDayForecast(id: Long) = throw UnsupportedOperationException() override fun requestForecastByZipCode(zipCode: Long, date: Long): ForecastList? { val result = ForecastRequest(zipCode.toString()).execute() val converted = dataMapper.convertToDomain(zipCode, result) forecastDb.saveForecast(converted) return forecastDb.requestForecastByZipCode(zipCode, date) }} ServerDataMapper服务的返回的数据模型,映射为本地需要的data.model.Forecast 12345678910111213141516171819class ServerDataMapper { fun convertToDomain(zipCode: Long, result: ForecastResult) = with(result) { ForecastList(zipCode, city.name, city.country, convertForecastListToDomain(list)) } private fun convertForecastListToDomain(list: List<Forecast>): List<ModelForecast> { return list.mapIndexed { index, forecast -> val dt = Calendar.getInstance().timeInMillis + TimeUnit.DAYS.toMillis(index.toLong()) convertForecastItemToDomain(forecast.copy(dt = dt)) } } private fun convertForecastItemToDomain(forecast: Forecast) = with(forecast) { ModelForecast(-1, dt, weather[0].description, temp.max.toInt(), temp.min.toInt(), generateIconUrl(weather[0].icon)) } private fun generateIconUrl(iconCode: String) = "http://openweathermap.org/img/w/$iconCode.png"} ForecastCommand 不会再直接与服务端交互,也不会转换数据到 domain model 1234567891011RequestForecastCommand(val zipCode: Long,val forecastProvider: ForecastProvider = ForecastProvider()) :Command<ForecastList> {companion object {val DAYS = 7}override fun execute(): ForecastList {return forecastProvider.requestByZipCode(zipCode, DAYS)}} FlowControl ranges If表达式 if 表达式总是返回一个value。如果一个分支返回了Unit,那整个表达式也将返回Unit,它是可以被忽略的 1234val z = if (condition) x else yval z1 = if (v1 > 3 && v1 < 7) v1 else 0val z1 = if (v1 in 4..6) v1 else 0 When表达式 对于默认的选项,我们可以增加一个 else 分支,它会在前面没有任何条件匹配时再执行。条件匹配成功后执行的代码也可以是代码块: 1234567891011121314when (x){ 1 -> print("x == 1") 2 -> print("x == 2") else -> { print("I'm a block") print("x is neither 1 nor 2") }}也可以返回一个值,条件可以被逗号分割val result = when (x) {0, 1 -> "binary"else -> "error"} 检测参数类型并进行判断,参数会被自动转型,所以你不需要去明确地做类型转换 123456when (view) { is TextView -> view.setText("I'm a TextView") is EditText -> toast("EditText value: ${view.getText()}") is ViewGroup -> toast("Number of children: ${view.childCount} ") else -> textView.visibility = View.GONE } 检测参数范围 1234567val cost = when(x) {in 1..10 -> "cheap"in 10..100 -> "regular"in 100..1000 -> "expensive"in specialValues -> "special value!"else -> "not rated"} 合并使用 123456valres=when{x in 1..10 -> "cheap"s.contains("hello") -> "it's a welcome!"v is ViewGroup -> "child count: ${v.getChildCount()}"else -> ""} For循环 1234567891011for (item in collection) {print(item)}for (index in 0..viewGroup.getChildCount() - 1) {val view = viewGroup.getChildAt(index)view.visibility = View.VISIBLE}for (i in array.indices)print(array[i]) While do/while 12345678while(x > 0){x--}do{val y = retrieveData()} while (y != null) // y在这里是可见的!While和do/while循环156 Range 表达式使用一个 .. 操作符,它是被定义实现了一个 RangTo 方法 1234567891011121314151617181920212223242526 if(i >= 0 && i <= 10) println(i) if (i in 0..10) println(i) for (i in 0..10) println(i)// Ranges 默认会自增长,所以如果像以下的代码 for (i in 10..0) println(i)// 可以使用 downTo 函数 for(i in 10 downTo 0) println(i)// 在 range 中使用 step 来定义一个从1到一个值的不同的空隙 for (i in 1..4 step 2) println(i) for (i in 4 downTo 1 step 2) println(i)// 创建一个open range(不包含最后一项,译者注:类似数学中的开区间),你可以使用 until 函数// 使用 (i in 0 until list.size) 比 (i in 0..list.size - 1) 更加容易理解 for (i in 0 until 4) println(i)// val views = (0..viewGroup.childCount - 1).map { viewGroup.getChildAt(it) } 泛型基础 创建一个指定泛型类,这个类现在可以使用任何的类型初始化,并且参数也会使用定义的类型 1234567class TypedClass<T>(parameter: T) {val value: T = parameter}val t1 = TypedClass("Hello World!")val t2 = TypedClass(25)val t3 = TypedClass<String?>(null) 接收一个null引用,那仍然还是需要指定它的类型 限制上一个类中为非null类型: 123class TypedClass<T : Any>(parameter: T) {val value: T = parameter} 如果我们只希望使用 Context 的子类 123class TypedClass<T : Context>(parameter: T) {val value: T = parameter} 构建泛型函数: 123fun <T> typedFunction(item: T): List<T> {...} 变体 增加一个 Integer 到 Object List,编译不通过 1234List<String> strList = new ArrayList<>();List<Object> objList = strList;objList.add(5);String str = objList.get(0); 以下编译通过,因为Collection 接口中的 void addAll(Collection<? extends E> items); 123List<String> strList = new ArrayList<>();List<Object> objList = new ArrayList<>();objList.addAll(strList); 增加 Strings 到另一个集合中唯一的限制就是那个集合接收 Strings 或者父类 1234void copyStrings(Collection<? super String> to, Collection<String> from) {to.addAll(from);} Kotlin仅仅使用 out 来针对协变( covariance )和使用 in 来针对逆变( contravariance )。在这个例子中,当我们类产生的对象可以被保存到弱限制的变量中,我们使用协变。我们可以直接在类中定义声明 123456789class TypedClass<out T>() {fun doSomething(): T {...}}这就是所有我们需要的。现在,在Java中不能编译的代码在Kotlin中可以完美运行:val t1 = TypedClass<String>()val t2: TypedClass<Any> = t1 泛型例子 let 它可以被任何对象调用。它接收一个函数(接收一个对象,返回函数结果)作为参数 123456public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this)} 替换if (forecast != null) dataMapper.convertDayToDomain(forecast) else null为forecast?.let { dataMapper.convertDayToDomain(it) } with 接收一个对象和一个函数,这个函数会作为这个对象的扩展函数执行。这表示我们根据推断可以在函数内使用 this,函数通过 f: T.() -> R 声明被定义成了扩展函数这就是为什么我们可以调用 receiver.f() 1inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f() 12345fun convertFromDomain(forecast: ForecastList) = with(forecast) {val daily = dailyForecast map { convertDayFromDomain(id, it)}CityForecast(id, city, country, daily)} apply 它看起来于with 很相似,但是是有点不同之处。 apply 可以避免创建builder的方式来使用,因为对象调用的函数可以根据自己的需要来初始化自己,只需要一个泛型类型,因为调用这个函数的对象也就是这个函数返回的对象 1inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this } apply 示例创建了一个 TextView ,修改了一些属性,然后赋值给一个变量 12345val textView = TextView(context).apply {text = "Hello"hint = "Hint"textColor = android.R.color.white} 在 ToolbarManager 中,我们使用这种方式来创建导航drawable: 12345private fun createUpDrawable() = with(DrawerArrowDrawable(toolbar.ctx)) {progress = 1fthis} 使用 with 和返回 this 是非常清晰的,但是使用 apply 可以更加简单: 1234private fun createUpDrawable() = DrawerArrowDrawable(toolbar.ctx).apply {progress = 1f} 其它概念 内部类 如果它是一个通常的类,它不能去访问外部类的成员,如果需要访问外部类的成员,我们需要用 inner 声明这个类 123456789101112131415class Outer1{ private val bar:Int=1 class Nested{ fun foo()=2 } } val d1=Outer1.Nested().foo() class Outer2{ private val bar:Int=1 inner class Nested{ fun foo()=bar } } val d2=Outer2().Nested().foo() 枚举 可以带参数 12345678910111213141516171819202122enum class Day {SUNDAY, MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY}enum class Icon(val res: Int) {UP(R.drawable.ic_up),SEARCH(R.drawable.ic_search),CAST(R.drawable.ic_cast)}val searchIconRes = Icon.SEARCH.res枚举可以通过 String 匹配名字来获取,我们也可以获取包含所有枚举的 Array ,所以我们可以遍历它val search: Icon = Icon.valueOf("SEARCH")val iconList: Array<Icon> = Icon.values()而且每一个枚举都有一些函数来获取它的名字、声明的位置val searchName: String = Icon.SEARCH.name()val searchPosition: Int = Icon.SEARCH.ordinal() 密封(Sealed)类 类似Scala中的 Option 类:这种类型可以防止null的使用,当对象包含一个值时返回 Some 类,当对象为空时则返回 None 1234sealed class Option<out T> {class Some<out T> : Option<T>()object None : Option<Nothing>()} 有一件关于密封类很不错的事情是当我们使用 when 表达式时,我们可以匹配所有选项而不使用 else 分支 1234val result = when (option) {is Option.Some<*> -> "Contains a value"is Option.None -> "Empty"} 异常 在Kotlin中,所有的 Exception 都是实现了 Throwable ,含有一个 message 且未经检查。这表示我们不会强迫我们在任何地方使用 try/catch 。这与Java中不太一样,比如在抛出 IOException 的方法,我们需要使用 try-catch 包围代码块。通过检查exception来处理显示并不是一个好的方法 123456789101112抛出异常的方式与Java很类似:throw MyException("Exception message")try 表达式也是相同的:try{// 一些代码}catch (e: SomeException) {// 处理}finally {// 可选的finally块} 在Kotlin中,throw和try都是表达式,这意味着它们可以被赋值给一个变量 123456789 val s = when(x){is Int -> "Int instance"is String -> "String instance"else -> throw UnsupportedOperationException("Not valid type")} val s = try { x as String } catch(e: ClassCastException) { null}]]></content>
<categories>
<category>Kotlin</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Kotlin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kotlin for Android(六)]]></title>
<url>%2F2018%2F03%2F24%2FKotlin-for-Android-%E5%85%AD%2F</url>
<content type="text"><![CDATA[Kotlin中的null安全 在Kotlin中一切都是对象(甚至是Java中原始数据类型),一切都是可null的。所以,当然我们可以有一个可null的integer,检查了一个对象的可null性,之后这个对象就会自动转型成不可null类型,这就是Kotlin编译器的智能转换,在 if 中, a 从 Int? 变成了 Int ,所以我们可以不需要再检查可null性而直接使用它,if 代码之外,又得检查处理,仅仅在变量当前不能被改变的时候才有效val 属性或者本地( val or var )变量,因为这个value可能被另外的线程修改,这时前面的检查会返回false 12345678910111213val a:Int?=null...if(a!=null){a.toString()}简化代码val a: Int? = null...a?.toString()val a:Int? = nullval myString = a?.toString() ?: "" throw 和 return 都是表达式 12val myString = a?.toString() ?: return falseval myString = a?.toString() ?: throw IllegalStateException() 我们确定我们是在用一个非null变量,但是他的类型却是可null的。我们可以使用 !!操作符来强制编译器执行可null类型时跳过限制检查,此时确定了不为null,直接定义为非null对象 12val a: Int? = nulla!!.toString() throw KotlinNullPointerException 可null性和Java库 Java中在一些获取对象的方法在Kotlin中显示返回 Any!这表示让开发者自己决定是否这个变量是否可null 123456public class NullTest { @Nullable public Object getObject(){ return ""; }} 编译不通过 比如重写 Activity 的 onCraete 函数,我们可以决定是否让 savedInstanceState 可null,都会被编译,但是第二种是错误的,因为一个Activity很可能接收到一个null的bundle1234override fun onCreate(savedInstanceState: Bundle?) {}override fun onCreate(savedInstanceState: Bundle) {}]]></content>
<categories>
<category>Kotlin</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Kotlin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kotlin for Android (五)]]></title>
<url>%2F2018%2F03%2F24%2FKotlin-for-Android-%E4%BA%94%2F</url>
<content type="text"><![CDATA[创建SQLiteHelper一般使用SqliteOpenHelper 去调用 getReadableDatabase() 或者 getWritableDatabase() ,然后我们可以执行我们的搜索并拿到结果。在这之后,我们不能忘记调用 close() ManagedSqliteOpenHelper 函数中,最后一行表示返回值。因为T没有任何的限制,所以我们可以返回任何对象。甚至如果我们不想返回任何值就使用 Unit,try-finally保证关闭数据库123456789101112val result = forecastDbHelper.use {val queriedObject = ...queriedObject}fun <T> use(f: SQLiteDatabase.() -> T): T { try { return openDatabase().f() } finally { closeDatabase() }} 定义表 CityForecastTable 提供了表的名字还有需要列:一个id(这个城市的zipCode),城市的名称和所在国家,DayForecast天气信息1234567891011121314151617object CityForecastTable {val NAME = "CityForecast"val ID = "_id"val CITY = "city"val COUNTRY = "country"}object DayForecastTable {val NAME = "DayForecast"val ID = "_id"val DATE = "date"val DESCRIPTION = "description"val HIGH = "high"val LOW = "low"val ICON_URL = "iconUrl"val CITY_ID = "cityId"} 实现SqliteOpenHelper1234567891011121314fun SQLiteDatabase.createTable(tableName: String, ifNotExists: Boolean = false, vararg columns: Pair<String, SqlType>) { val escapedTableName = tableName.replace("`", "``") val ifNotExistsText = if (ifNotExists) "IF NOT EXISTS" else "" execSQL( columns.map { col -> "${col.first} ${col.second.render()}" }.joinToString(", ", prefix = "CREATE TABLE $ifNotExistsText `$escapedTableName`(", postfix = ");") )}db.createTable(CityForecastTable.NAME, true,Pair(CityForecastTable.ID, INTEGER + PRIMARY_KEY),Pair(CityForecastTable.CITY, TEXT),Pair(CityForecastTable.COUNTRY, TEXT)) 第一个参数是表的名称第二个参数,当是true的时候,创建之前会检查这个表是否存在。第三个参数是一个 Pair 类型的 vararg 参数。 vararg 也存在在Java中,这是一种在一个函数中传入联系很多相同类型的参数。这个函数也接收一个对象数组。Anko中有一种叫做 SqlType 的特殊类型,它可以与 SqlTypeModifiers 混合,比如 PRIMARY_KEY 。 + 操作符像之前那样被重写了。这个 plus 函数会把两者通过合适的方式结合起来,然后返回一个新的 SqlType1234fun SqlType.plus(m: SqlTypeModifier) : SqlType {return SqlTypeImpl(name, if (modifier == null) m.toString()else "$modifier $m")} public fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)因为带有一个函数参数的函数可以被用于inline,所以结果非常清晰:val pair = object1 to object2 简化后的代码如下 1234567891011121314151617181920212223242526272829303132class ForecastDbHelper(ctx: Context = App.instance) : ManagedSQLiteOpenHelper(App.instance, ForecastDbHelper.DB_NAME, null, ForecastDbHelper.DB_VERSION) { override fun onCreate(db: SQLiteDatabase) { db.createTable(CityForecastTable.NAME, true, CityForecastTable.ID to INTEGER + PRIMARY_KEY, CityForecastTable.CITY to TEXT, CityForecastTable.COUNTRY to TEXT) db.createTable(DayForecastTable.NAME, true, DayForecastTable.ID to INTEGER + PRIMARY_KEY, DayForecastTable.DATE to INTEGER, DayForecastTable.DESCRIPTION to TEXT, DayForecastTable.HIGH to INTEGER, DayForecastTable.LOW to INTEGER, DayForecastTable.ICON_URL to TEXT, DayForecastTable.CITY_ID to INTEGER) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { db.dropTable(CityForecastTable.NAME, true) db.dropTable(DayForecastTable.NAME, true) onCreate(db) } companion object { val DB_NAME = "forecast.db" val DB_VERSION = 1 val instance: ForecastDbHelper by lazy { ForecastDbHelper() } }} instance 这个属性使用了 lazy 委托,它表示直到它真的被调用才会被创建。如果数据库没有被使用,就没有去创建这个对象。一般 lazy 委托的代码块可以阻止在多个不同的线程中创建多个对象。这个只会发生在两个线程在同时访问这个 instance 对象, lazy 委托是线程安全的 集合和函数操作符 本地接口 Iterable:父类。所有我们可以遍历一系列的都是实现这个接口MutableIterable:一个支持遍历的同时可以执行删除的IterablesCollection:这个类相是一个范性集合。我们通过函数访问可以返回集合的size、是否为空、是否包含一个或者一些item。这个集合的所有方法提供查询,因为collection是不可修改的MutableCollection:一个支持增加和删除item的Collection。它提供了额外的函数,比如 add 、 remove 、 clear 等等List:可能是最流行的集合类型。它是一个范性有序的集合。因为它的有序,我们可以使用 get 函数通过position来访问MutableList:一个支持增加和删除item的ListSet:一个无序并不支持重复item的集合MutableSet:一个支持增加和删除item的SetMap:一个key-value对的collection。key在map中是唯一的,也就是说不能有两对key是一样的键值对存在于一个map中MutableMap:一个支持增加和删除item的map 总数操作符 any 如果至少有一个元素符合给出的判断条件,则返回true 123val list = listOf(1, 2, 3, 4, 5, 6)assertTrue(list.any { it % 2 == 0 }) trueassertFalse(list.any { it > 10 }) false all 如果全部的元素符合给出的判断条件,则返回true 12assertTrue(list.all { it < 10 }) trueassertFalse(list.all { it % 2 == 0 }) false count 返回符合给出判断条件的元素总数 1assertEquals(3, list.count { it % 2 == 0 }) true fold 在一个初始值的基础上从第一项到最后一项通过一个函数累计所有的元素,所有元素相加,并加上初始值 1assertEquals(25, list.fold(4) { total, next -> total + next }) true foldRight 与 fold 一样,但是顺序是从最后一项到第一项 foreEach 遍历所有元素,并执行给定的操作 1list.forEach { println(it) } forEachIndexed 与 forEach ,但是我们同时可以得到元素的index 1list.forEachIndexed { index, i -> println("position $index value $i") } max/min 返回最大值/最小值,没有返回null maxBy/minBy 根据给定的函数返回最大/最小的一项,如果没有则返回null 12// The element whose negative is greaterassertEquals(1, list.maxBy { -it }) none 如果没有任何元素与给定的函数匹配,则返回true 12// No elements are divisible by 7assertTrue(list.none { it % 7 == 0 }) true reduce与 fold 一样,但是没有一个初始值。通过一个函数从第一项到最后一项进行累计 1assertEquals(21, list.reduce { total, next -> total + next }) reduceRight与 reduce 一样,但是顺序是从最后一项到第一项 12assertEquals(21, list.reduceRight { total, next -> total + next}) sumBy返回所有每一项通过函数转换之后的数据的总和 1assertEquals(3, list.sumBy { it % 2 }) 过滤操作符 drop返回包含去掉前n个元素的所有元素的列表 1assertEquals(listOf(5, 6), list.drop(4)) dropWhile返回根据给定函数从第一项开始去掉指定元素的列表 1assertEquals(listOf(3, 4, 5, 6), list.dropWhile { it < 3 }) dropLastWhile返回根据给定函数从最后一项开始去掉指定元素的列表 1assertEquals(listOf(1, 2, 3, 4), list.dropLastWhile { it > 4 }) filter过滤所有符合给定函数条件的元素 1assertEquals(listOf(2, 4, 6), list.filter { it % 2 == 0 }) 1234val listWithNull = listOf(1, 2, null)Log.e(tag, "filterNotNull " + listWithNull.filterNotNull())filterNotNull [1, 2] slice 过滤一个list中指定index的元素 123Log.e(tag, "slice " + list.slice(listOf(1, 2, 4)))slice [2, 3, 5] take返回从第一个开始的n个元素, takeLast返回从最后一个开始的n个元素,takeWhile返回从第一个开始符合给定函数条件的元素 123assertEquals(listOf(1, 2), list.take(2))assertEquals(listOf(5, 6), list.takeLast(2))assertEquals(listOf(1, 2), list.takeWhile { it < 3 }) flatMap遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中 12assertEquals(listOf(1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7),list.flatMap { listOf(it, it + 1) }) groupBy 返回一个根据给定函数分组后的map 12assertEquals(mapOf("odd" to listOf(1, 3, 5), "even" to listOf(2,4, 6)), list.groupBy { if (it % 2 == 0) "even" else "odd" }) map返回一个每一个元素根据给定的函数转换所组成的List 1assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it * 2 }) mapIndexed返回一个每一个元素根据给定的包含元素index的函数转换所组成的List 12assertEquals(listOf (0, 2, 6, 12, 20, 30), list.mapIndexed { index, it -> index * it }) mapNotNull返回一个每一个非null元素根据给定的函数转换所组成的List 12assertEquals(listOf(2, 4, 6, 8), listWithNull.mapNotNull { it * 2}) 示例 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 val list = listOf(1, 1, 3, 4, 5, 6) Log.e(tag, "any1 " + list.any { it % 2 == 0 }) Log.e(tag, "any2 " + list.any { it > 10 }) Log.e(tag, "all1 " + list.all { it < 10 }) Log.e(tag, "all2 " + list.all { it % 2 == 0 }) Log.e(tag, "count " + list.count { it % 2 == 0 }) Log.e(tag, "fold " + list.fold(4) { total, next -> total + next }) Log.e(tag, "foldRight " + list.foldRight(4) { total, next -> total + next }) list.forEach { println(it) } list.forEachIndexed { index, i -> println("position $index value $i") } Log.e(tag, "max " + list.max()) Log.e(tag, "maxBy " + list.maxBy { -it }) Log.e(tag, "min " + list.min()) Log.e(tag, "minBy " + list.minBy { -it }) Log.e(tag, "none " + list.none { it % 7 == 0 }) Log.e(tag, "reduce " + list.reduce { total, next -> total + next }) Log.e(tag, "reduce right " + list.reduceRight { total, next -> total + next }) Log.e(tag, "sumBy " + list.sumBy { it % 2 }) Log.e(tag, "drop " + list.drop(4)) Log.e(tag, "dropWhile " + list.dropWhile { it > 2 }) Log.e(tag, "dropLastWhile " + list.dropLastWhile { it > 2 }) Log.e(tag, "filter " + list.filter { it > 2 }) Log.e(tag, "filterNot " + list.filterNot { it > 2 }) val listWithNull = listOf(1, 2, null) Log.e(tag, "filterNotNull " + listWithNull.filterNotNull()) Log.e(tag, "slice " + list.slice(listOf(1, 2, 4))) Log.e(tag, "flatMap " + list.flatMap { listOf(it, it + 1) }) Log.e(tag, "groupBy " + list.groupBy { if (it % 2 == 0) "even" else "odd" }) Log.e(tag, "map " + list.map { it * 2 }) Log.e(tag, "partition " + list.partition { it % 2 == 0 }) Log.e(tag, "plus " + list + listOf(7, 8)) Log.e(tag, "zip " + list.zip(listOf(7, 4, 9, 8))) Log.e(tag, "unzip " + listOf(Pair(5, 7), Pair(6, 8)).unzip())E/MainActivity: any1 true any2 false all1 true all2 false count 2 fold 24 foldRight 24I/System.out: 1 1 3 4 5 6 position 0 value 1 position 1 value 1 position 2 value 3 position 3 value 4 position 4 value 5 position 5 value 6E/MainActivity: max 6 maxBy 1 min 1E/MainActivity: minBy 6 none true reduce 20 reduce right 20 sumBy 4 drop [5, 6] dropWhile [1, 1, 3, 4, 5, 6] dropLastWhile [1, 1] filter [3, 4, 5, 6] filterNot [1, 1] filterNotNull [1, 2] slice [1, 3, 5] flatMap [1, 2, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7] groupBy {odd=[1, 1, 3, 5], even=[4, 6]} map [2, 2, 6, 8, 10, 12] partition ([4, 6], [1, 1, 3, 5]) plus [1, 1, 3, 4, 5, 6][7, 8] zip [(1, 7), (1, 4), (3, 9), (4, 8)] unzip ([5, 6], [7, 8]) 元素操作符 生产操作符 顺序操作符 创建数据库model123456789101112131415161718192021222324252627282930313233343536373839/** * SQLite表与对象之间的互相映射 */class CityForecast(val map: MutableMap<String, Any?>, val dailyForecast: List<DayForecast>) { var _id: Long by map var city: String by map var country: String by map constructor(id: Long, city: String, country: String, dailyForecast: List<DayForecast>) : this(HashMap(), dailyForecast) { this._id = id this.city = city this.country = country }}/**不同之处就是不需要设置id,因为它将通过SQLite自增长*/class DayForecast(val map: MutableMap<String, Any?>) { var _id: Long by map var date: Long by map var description: String by map var high: Long by map var low: Long by map var iconUrl: String by map var cityId: Long by map constructor(date: Long, description: String, high: Long, low: Long, iconUrl: String, cityId: Long) : this(HashMap()) { this.date = date this.description = description this.high = high this.low = low this.iconUrl = iconUrl this.cityId = cityId }} 写入和查询数据库123456789101112131415161718192021222324252627282930313233class ForecastDb(val forecastDbHelper: ForecastDbHelper = ForecastDbHelper.instance, val dataMapper: DbDataMapper = DbDataMapper()) { //dailyRequest 是查询语句中 where 的一部分。它是 whereSimple 函 //数需要的第一个参数,这与我们用一般的helper做的方式很相似 fun requestForecastByZipcode(zipCode: String, date: Date) = forecastDbHelper.use { val dailyRequest = "${DayForecastTable.CITY_ID}=? " + "AND ${DayForecastTable.DATE}>=?" val dailyForecast = select(DayForecastTable.NAME) .whereSimple(dailyRequest, zipCode, date.toString()) .parseList { DayForecast(HashMap(it)) }// 简化写法// val dailyRequest = "${DayForecastTable.CITY_ID} = {id}" + "AND $// {DayForecastTable.DATE} >= {date}"// val dailyForecast = select(DayForecastTable.NAME)// .where(dailyRequest, "id" to zipCode, "date" to date)// .parseList { DayForecast(HashMap(it)) } val city = select(CityForecastTable.NAME) .whereSimple("${CityForecastTable.ID} = ?", zipCode) .parseOpt { CityForecast(HashMap(it), dailyForecast) } city?.let { dataMapper.convertToDomain(it) } } fun saveForecast(forecastList: ForecastList) = forecastDbHelper.use { clear(DayForecastTable.NAME) clear(CityForecastTable.NAME) with(dataMapper.convertFromDomain(forecastList)) { insert(CityForecastTable.NAME, *map.toVarargArray()) dailyForecast.forEach { insert(DayForecastTable.NAME, *it.map.toVarargArray()) } } }} DbDataMapper使用with简化 1234567891011121314151617181920class DbDataMapper { fun convertFromDomain(forecastList: ForecastList) = with(forecastList) { val daily = dailyForecast.map { convertDayFromDomain(id, it) } CityForecast(id, city, country, daily) } private fun convertDayFromDomain(cityId: Long, forecast: Forecast) = with(forecast) { DayForecast(date, description, high, low, iconUrl, cityId) } fun convertToDomain(forecast: CityForecast) = with(forecast) { val daily = dailyForecast.map { convertDayToDomain(it) } ForecastList(_id, city, country, daily) } fun convertDayToDomain(dayForecast: DayForecast) = with(dayForecast) { Forecast(_id, date, description, high, low, iconUrl) }} Map,SelectQueryBuilder 扩展函数 它需要一个表名和一个 vararg 修饰的 Pair<String, Any> 作为参数。这个函数会把 vararg 转换成Android SDK需要的 ContentValues 对象。所以任务组成是把 map 转换成一个 vararg 数组,在toVarargArray 函数结果前面使用 * 表示这个array会被分解成为一个 vararg 参数,这个在Java中是自动处理的,但是我们需要在Kotlin中明确指明,通过 map 的使用,可以用很简单的方式把类转换为数据表 12345678910111213141516fun <K, V : Any> Map<K, V?>.toVarargArray(): Array<out Pair<K, V>> = map({ Pair(it.key, it.value!!) }).toTypedArray()fun <T : Any> SelectQueryBuilder.parseList(parser: (Map<String, Any?>) -> T): List<T> = parseList(object : MapRowParser<T> { override fun parseRow(columns: Map<String, Any?>): T = parser(columns) })fun <T : Any> SelectQueryBuilder.parseOpt(parser: (Map<String, Any?>) -> T): T? = parseOpt(object : MapRowParser<T> { override fun parseRow(columns: Map<String, Any?>): T = parser(columns) })fun SQLiteDatabase.clear(tableName: String) { execSQL("delete from $tableName")}]]></content>
<categories>
<category>Kotlin</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Kotlin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kotlin for Android (四)]]></title>
<url>%2F2018%2F03%2F23%2Fkotlin-for-Android-%E5%9B%9B%2F</url>
<content type="text"><![CDATA[Application单例化和属性的Delegated需要有一个更简单的访问application context的方式 Application单例化12345678910class App : Application() {companion object {private var instance: Application? = nullfun instance() = instance!!}override fun onCreate() {super.onCreate()instance = this}} Android有一个问题,就是我们不能去控制很多类的构造函数。比如,我们不能初始化一个非null属性,因为它的值需要在构造函数中去定义。所以我们需要一个可null的变量,和一个返回非null值的函数。我们知道我们一直都有一个 App 实例,但是在它调用 onCreate 之前我们不能去操作任何事情,所以我们为了安全性,我们假设 instance() 函数将会总是返回一个非null的 app 实例。但是这个方案看起来有点不自然。我们需要定义个一个属性(已经有了getter和setter),然后通过一个函数来返回那个属性。我们有其他方法去达到相似的效果么?是的,我们可以通过委托这个属性的值给另外一个类。这个就是我们知道的委托属性 委托属性 属性委托的结构如下:这个T是委托属性的类型。 getValue 函数接收一个类的引用和一个属性的元数据。 setValue 函数又接收了一个被设置的值。如果这个属性是不可修改(val),就会只有一个 getValue 函数 123456789class Delegate<T> : ReadWriteProperty<Any?, T> {fun getValue(thisRef: Any?, property: KProperty<*>): T {return ...}fun setValue(thisRef: Any?, property: KProperty<*>, value: T){...}} 示例属性委托怎么设置,by 这个关键字来指定一个委托对象 123class Example {var p: String by Delegate()} 标准委托 Lazy它包含一个lambda,当第一次执行 getValue 的时候这个lambda会被调用,所以这个属性可以被延迟初始化。之后的调用都只会返回同一个值,当我们在它们第一次真正调用之前不是必须需要它们的时候。我们可以节省内存,在这些属性真正需要前不进行初始化,database并没有被真正初始化,直到第一次调用 onCreate 时。在那之后,我们才确保applicationContext存在,并且已经准备好可以被使用了。 lazy 操作符是线程安全的。如果你不担心多线程问题或者想提高更多的性能,你也可以使用lazy(LazyThreadSafeMode.NONE){ … } 123456789class App : Application() {val database: SQLiteOpenHelper by lazy {MyDatabaseHelper(applicationContext)}override fun onCreate() {super.onCreate()val db = database.writableDatabase}} Observable这个委托会帮我们监测我们希望观察的属性的变化。当被观察属性的 set 方法被调用的时候,它就会自动执行我们指定的lambda表达式。所以一旦该属性被赋了新的值,我们就会接收到被委托的属性、旧值和新值,下面的示例是一些我们需要关心的ViewMode,每次值被修改了,就会保存它们到数据库 123456class ViewModel(val db: MyDatabase) {var myProperty by Delegates.observable("") {d, old, new ->db.saveChanges(this, new)}} Vetoable 这是一个特殊的 observable ,它让你决定是否这个值需要被保存。它可以被用于在真正保存之前进行一些条件判断,下面的示例,允许在新的值是正数的时候执行保存。在lambda中,最后一行表示返回值。你不需要使用return关键字(实质上不能被编译) 1234var positiveNumber = Delegates.vetoable(0) {d, old, new ->new >= 0} NotNull 含有一个可null的变量并会在我们设置这个属性的时候分配一个真实的值。如果这个值在被获取之前没有被分配,它就会抛出一个异常 123456789class App : Application() {companion object {var instance: App by Delegates.notNull()}override fun onCreate() {super.onCreate()instance = this}} 从Map中映射值 属性的值会从一个map中获取value,属性的名字对应这个map中的key,如果我们importkotlin.properties.getValue ,我们可以从构造函数映射到 val 属性来得到一个不可修改的map。如果我们想去修改map和属性,我们也可以import kotlin.properties.setValue 。类需要一个 MutableMap 作为构造函数的参数 想象我们从一个Json中加载了一个配置类,然后分配它们的key和value到一个map中。我们可以仅仅通过传入一个map的构造函数来创建一个实例: 1234567import kotlin.properties.getValueclass Configuration(map: Map<String, Any?>) {val width: Int by mapval height: Int by mapval dp: Int by mapval deviceName: String by map} 创建一个map 123456conf = Configuration(mapOf("width" to 1080,"height" to 720,"dp" to 240,"deviceName" to "mydevice")) 自定义委托 创建一个类然后继承 ReadWriteProperty : 12345678910private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> {override fun getValue(thisRef: Any?, property: KProperty<*>): T {throw UnsupportedOperationException()}override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {}} 这个委托可以作用在任何非null的类型。它接收任何类型的引用,然后像getter和setter那样使用T。现在我们需要去实现这些函数 Getter函数 如果已经被初始化,则会返回一个值,否则会抛异常Setter函数 如果仍然是null,则赋值,否则会抛异常 12345678910111213141516private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> {private var value: T? = nulloverride fun getValue(thisRef: Any?, property: KProperty<*>): T {return value ?: throw IllegalStateException("${desc.name} " +"not initialized")}override fun setValue(thisRef: Any?, property: KProperty<*>,value: T) {this.value = if (this.value == null) valueelse throw IllegalStateException("${desc.name} already initialized")}} 使用上述委托示例 1234object DelegatesExt {fun notNullSingleValue<T>():ReadWriteProperty<Any?, T> = NotNullSingleValueVar()} 重新实现Application单例化,可以在app的任何地方去修改这个值,因为如果我们使用 Delegates.notNull() ,属性必须是var的 1234567891011121314class App : Application() { companion object { var instance: App by Delegates.notNull() } override fun onCreate() { super.onCreate() instance = this }}上述可以任何地方修改问题,解决办法使用下面的委托,只能修改一次companion object {var instance: App by DelegatesExt.notNullSingleValue()}]]></content>
<categories>
<category>Kotlin</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Kotlin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kotlin for Android (三)]]></title>
<url>%2F2018%2F03%2F23%2FKotlin-for-Android-%E4%B8%89%2F</url>
<content type="text"><![CDATA[操作符 操作符在Adapter中的应用 12345678910// val size: Int get() = dailyForecast.sizeoverride fun getItemCount(): Int = items.size// operator fun get(position: Int) = dailyForecast[position]override fun onBindViewHolder(holder: VH, position: Int) { with(items[position]) {// with(items.dailyForecast[position]) { holder.textView.text = "$date - $description - $high - $low" }} 扩展函数中的操作符,访问ViewGroup中的View 12345operator fun ViewGroup.get(position: Int): View = getChildAt(position)val container: ViewGroup = find(R.id.container)val view = container[2] 使Forecast list可点击 item_forecast.xml 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/spacing_xlarge" android:background="?attr/selectableItemBackground" android:gravity="center_vertical" android:orientation="horizontal"> <ImageView android:id="@+id/icon" android:layout_width="48dp" android:layout_height="48dp" tools:src="@mipmap/ic_launcher"/> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginLeft="@dimen/spacing_xlarge" android:layout_marginRight="@dimen/spacing_xlarge" android:orientation="vertical"> <TextView android:id="@+id/date" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.AppCompat.Medium" tools:text="May 14, 2015"/> <TextView android:id="@+id/description" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.AppCompat.Caption" tools:text="Light Rain"/> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:id="@+id/maxTemperature" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.AppCompat.Medium" tools:text="30"/> <TextView android:id="@+id/minTemperature" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.AppCompat.Caption" tools:text="15"/> </LinearLayout></LinearLayout> 添加iconUrl参数 123456789101112131415data class Forecast(val date: String, val description: String,val high: Int, val low: Int, val iconUrl: String)在 ForecastDataMapper 中:private fun convertForecastItemToDomain(forecast: Forecast): ModelForecast {return ModelForecast(convertDate(forecast.dt),forecast.weather[0].description, forecast.temp.max.toInt(),forecast.temp.min.toInt(), generateIconUrl(forecast.weather[0].icon))}private fun generateIconUrl(iconCode: String): String= "http://openweathermap.org/img/w/$iconCode.png" Adapter添加点击事件,对应操作符invoke,可以有两种方式调用 itemClick.invoke(forecast)itemClick(forecast) 1234public interface OnItemClickListener { //对应的操作符为 itemClick(forecast) operator fun invoke(forecast: Forecast)} 新的Adapter,重新设置布局,绑定数据,设置监听 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354class ForecastListAdapter(val items: ForecastList, val itemClick: OnItemClickListener) : RecyclerView.Adapter<ForecastListAdapter.VH>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_forecast, parent, false) return VH(view, itemClick) } // val size: Int get() = dailyForecast.size override fun getItemCount(): Int = items.size override fun onBindViewHolder(holder: VH, position: Int) { bindForecast(position, holder) } // operator fun get(position: Int) = dailyForecast[position] private fun bindForecast(position: Int, holder: VH) { with(items[position]) { // with(items.dailyForecast[position]) {// holder.itemView.date.text = "$date - $description - $high - $low" holder.bindForecast(this) } } class VH(itemView: View, val itemClick: OnItemClickListener) : RecyclerView.ViewHolder(itemView) { private val iconView: ImageView private val dateView: TextView private val descriptionView: TextView private val maxTemperatureView: TextView private val minTemperatureView: TextView init { iconView = itemView.findViewById(R.id.icon) dateView = itemView.findViewById(R.id.date) descriptionView = itemView.findViewById(R.id.description) maxTemperatureView = itemView.findViewById(R.id.maxTemperature) minTemperatureView = itemView.findViewById(R.id.minTemperature) } fun bindForecast(forecast: Forecast) { with(forecast) { Picasso.with(itemView.context).load(iconUrl).into(iconView) dateView.text = date descriptionView.text = description maxTemperatureView.text = high.toString() minTemperatureView.text = low.toString() itemView.setOnClickListener { itemClick(this) } } } } public interface OnItemClickListener { //对应的操作符为 itemClick(forecast) operator fun invoke(forecast: Forecast) }} 设置Adapter,创建一个匿名内部类,我们去创建了一个实现了刚刚创建的接口的对象。需要使用另一个强大的函数式编程的特性,把这些代码转换得更简单 1234567uiThread { recyclerView.adapter = ForecastListAdapter(result, object : ForecastListAdapter.OnItemClickListener { override fun invoke(forecast: Forecast) { toast(forecast.date) } }) } ViewExtensions.kt 通过 ctx 这个属性来返回context,但是在View中缺少这个属性。所以我们要创建一个新的名叫 ViewExtensions.kt 文件来代替 ui.utils ,然后增加这个扩展属性val View.ctx: Contextget() = context从现在开始,任何View都可以使用这个属性了。这个不是必须的,因为你可以使用扩展的context属性,但是我觉得如果我们使用 ctx 的话在其它类中也会更有连贯性。而且,这是一个很好的怎么去使用扩展属性的例子 Lambdas 一个lambda表达式通过参数的形式被定义在箭头的左边(被圆括号包围),然后在箭头的右边返回结果值,接收一个View,然后返回一个Unit 123456789101112fun setOnClickListener(listener: (View) -> Unit)view.setOnClickListener({ view -> toast("Click")})如果参数没有使用,可以省略参数view.setOnClickListener({ toast("Click") })如果函数的最后一个参数是函数,可以把这个函数参数移到括号外view.setOnClickListener() { toast("Click") }这个函数只有一个参数,省略圆括号view.setOnClickListener { toast("Click") } 所以MainActivity中设置ForecaAdapter 1234recyclerView.adapter = ForecastListAdapter(result) { forecast -> toast(forecast.date) }如果这个函数只有一个参数,可以使用it引用,不用去指定左边的参数val adapter = ForecastListAdapter(result) { toast(it.date) } 扩展语言 with函数 1234567@kotlin.internal.InlineOnlypublic inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block()} 第二个参数是一个函数,可以把他放在圆括号外面,所以可以创建一个代码块 12345678 with(forecast) {Picasso.with(itemView.ctx).load(iconUrl).into(iconView)dateView.text = datedescriptionView.text = descriptionmaxTemperatureView.text = "$high"minTemperatureView.text = "$low"itemView.setOnClickListener { itemClick(this) }} 内联函数内联函数与普通的函数有点不同。一个内联函数会在编译的时候被替换掉,而不是真正的方法调用。这在一些情况下可以减少内存分配和运行时开销。举个例子,如果我们有一个函数,只接收一个函数作为它的参数。如果是一个普通的函数,内部会创建一个含有那个函数的对象。另一方面,内联函数会把我们调用这个函数的地方替换掉,所以它不需要为此生成一个内部的对象 判断版本执行代码 12345678910inline fun supportsLollipop(code: () -> Unit) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {code()}}它只是检查版本,然后如果满足条件则去执行。现在我们可以这么做:supportsLollipop {window.setStatusBarColor(Color.BLACK)} 可见性修饰符 protected这个修饰符只能被用在类或者接口中的成员上。一个包成员不能被定义为 protected ,定义在一个成员中,就与Java中的方式一样了:它可以被成员自己和继承它的成员可见(比如,类和它的子类) internal如果是一个定义为 internal 的包成员的话,对所在的整个 module 可见。如果它是一个其它领域的成员,它就需要依赖那个领域的可见性了。比如,如果我们写了一个 private 类,那么它的 internal 修饰的函数的可见性就会限制与它所在的这个类的可见性。我们可以访问同一个 module 中的 internal 修饰的类,但是不能访问其它 module 的 构造器 所有构造函数默认都是 public 的,它们类是可见的,可以被其它地方使用。我们也可以使用这个语法来把构造函数修改为 private :class C private constructor(a: Int) { ... } 布局中的\<include>增加内嵌布局,需要手动import 12import kotlinx.android.synthetic.activity_main.*import kotlinx.android.synthetic.content_main.* 绑定一个xml中的view到另一个view。唯一不同的就是需要 import :import kotlinx.android.synthetic.view_item.view.* 在Adapter中可以简化代码 1234567891011121314import kotlinx.android.synthetic.main.item_forecast.view.*class VH(itemView: View, val itemClick: (Forecast) -> Unit) : RecyclerView.ViewHolder(itemView) { fun bindForecast(forecast: Forecast) { with(forecast) { Picasso.with(itemView.context).load(iconUrl).into(itemView.icon) itemView.date.text = date itemView.description.text = description itemView.maxTemperature.text = high.toString() itemView.minTemperature.text = low.toString() itemView.setOnClickListener { itemClick(this) } } } }]]></content>
</entry>
<entry>
<title><![CDATA[Kotlin for Android (二)]]></title>
<url>%2F2018%2F03%2F23%2FKotlin-for-Android-%E4%BA%8C%2F</url>
<content type="text"><![CDATA[Anko Anko Commons:一个轻量级的库,里面包含了intents,对话框,日志等帮助类 Anko Layouts:用于编写动态Android布局的快速且类型安全的方法 Anko SQLite:查询适用于Android SQLite的DSL和分析器集合 Anko Coroutines:基于kotlinx.coroutines库的实用程序 简化获取RecyclerView1val forecastList: RecyclerView = find(R.id.recyclerView) 扩展函数扩展函数数是指在一个类上增加一种新的行为,甚至我们没有这个类代码的访问权限。这是一个在缺少有用函数的类上扩展的方法。在Java中,通常会实现很多带有static方法的工具类。Kotlin中扩展函数的一个优势是我们不需要在调用方法的时候把整个对象当作参数传入。扩展函数表现得就像是属于这个类的一样,而且我们可以使用 this 关键字和调用所有public方法123fun Context.toastF(message: CharSequence,duration: Int=Toast.LENGTH_SHORT){ Toast.makeText(this, "$message", duration).show()} Anko已经扩展toast函数,提供了CharSequence,(resource id)Int的函数 12toast(R.string.app_name)longToast("longToast") 扩展函数并不是真正地修改了原来的类,它是以静态导入的方式来实现的。扩展函数可以被声明在任何文件中,通用的实践是把一系列有关的函数放在一个新建的文件里 123var TextView.text: CharSequence get() = getText() set(v) = setText(v) 网络请求 网络请求类 1234567public class Request(var url: String) { public fun run() { //这个方法不推荐 val forcastJson = URL(url).readText() Log.e(javaClass.simpleName, forcastJson) }} 执行异步请求,async用于其他线程执行请求,uiThread回到主线程,UIThread 是可以依赖于调用者如果它是被一个Activity 调用的,那么如果 activity.isFinishing() 返回true,则 uiThread 不会执行,在Activity销毁的时候避免程序崩溃 123456doAsync { val url="http://samples.openweathermap.org/data/2.5/weather?id=2172797&appid=b6907d289e10d714a6e88b30761fae22" Request(url).run() uiThread { longToast("Request Performed") } } 数据类,属性访问,以及其他函数 equals(): 它可以比较两个对象的属性来确保他们是相同的 hashCode(): 我们可以得到一个hash值,也是从属性中计算出来的 copy(): 你可以拷贝一个对象,可以根据你的需要去修改里面的属性 一系列可以映射对象到变量中的函数12data class Forecast(val date: Date, val temperature: Float, valdetails: String) 复制一个数据类,修改某个属性 1234567val f1 = Forecast(Date(), 23f, "Sunny")Log.e(tag, f1.toString())val f2 = f1.copy(temperature = 30f)Log.e(tag, f2.toString())E/MainActivity: Forecast(date=Fri Mar 23 00:22:42 EDT 2018, temperature=23.0, details=Sunny)E/MainActivity: Forecast(date=Fri Mar 23 00:22:42 EDT 2018, temperature=30.0, details=Sunny) 映射对象到变量中 12345678910111213//映射对象到变量中,多声明val (date, temp, details) = f1Log.e(tag, "date " + date.toString())Log.e(tag, "temp " + temp.toString())Log.e(tag, "details $details")// 上面的多声明会编译成下面的代码// val date = f1.component1()// val temperature = f1.component2()// val details = f1.component3()E/MainActivity: date Fri Mar 23 00:33:41 EDT 2018E/MainActivity: temp 23.0E/MainActivity: details Sunny Map 类含有一些扩展函数的实现,允许它在迭代时使用key和value 1234567val map = mutableListOf(f1,f2)for ((key, value) in map) {Log.d("map", "key:$key, value:$value")}E/map: key:Fri Mar 23 00:41:25 EDT 2018, value:23.0E/map: key:Fri Mar 23 00:41:25 EDT 2018, value:30.0 转换josn到数据类,这里有个插件JsonToKotlinClass,转换json to kotlin data class 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748data class ForecastResult( val city: City, val cod: String, val message: Double, val cnt: Int, val list: List<Item8>)data class City( val id: Int, val name: String, val coord: Coord, val country: String, val population: Int)data class Coord( val lon: Double, val lat: Double)data class Item8( val dt: Int, val temp: Temp, val pressure: Double, val humidity: Int, val weather: List<Weather>, val speed: Double, val deg: Int, val clouds: Int)data class Temp( val day: Double, val min: Double, val max: Double, val night: Double, val eve: Double, val morn: Double)data class Weather( val id: Int, val main: String, val description: String, val icon: String) Companion objects (伴随对象)Kotlin允许我们去定义一些行为与静态对象一样的对象。尽管这些对象可以用众所周知的模式来实现,比如容易实现的单例模式。我们需要一个类里面有一些静态的属性、常量或者函数,我们可以使用 companion objecvt 。这个对象被这个类的所有对象所共享,就像Java中的静态属性或者方法 1234567891011121314151617public class Request(var zipCode: String) { companion object { private val APP_ID = "15646a06818f61f7b8d7823ca833e1ce" private val URL = "http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7" private val COMPLETE_URL = "$URL&APPID=$APP_ID&zip=" } public fun execute(): ForecastResult { //这个方法不推荐 val forecastJson = URL(COMPLETE_URL + zipCode).readText() Log.e(javaClass.simpleName, forecastJson) return Gson().fromJson(forecastJson, ForecastResult::class.java) }} val result = Request(94040).execute() Log.e(tag, "result " + result.toString()) 构建domain层 定义一个commnad,执行一个任务,返回泛型,一起Kotlin函数都有返回值,没有指定默认返回Unit,所以不返回数据可以指定类型会Unit 123public interface Command<T> { fun execute(): T} command需要去请求天气预报接口然后转换结果为domain类 1234567public interface Command<T> { data class ForecastList(val city: String, val country: String, val dailyForecast: List<Forecast>) data class Forecast(val date: String, val description: String, val high: Int, val low: Int)} 数据映射到Domain,创建DataMapper 123456789101112131415161718192021222324public class ForecastDataMapper {fun convertFromDataModel(forecast: ForecastResult): ForecastList {return ForecastList(forecast.city.name, forecast.city.country,convertForecastListToDomain(forecast.list))private fun convertForecastListToDomain(list: List<Forecast>):List<ModelForecast> {return list.map { convertForecastItemToDomain(it) }}private fun convertForecastItemToDomain(forecast: Forecast):ModelForecast {return ModelForecast(convertDate(forecast.dt),forecast.weather[0].description, forecast.temp.max.toInt(),forecast.temp.min.toInt())}private fun convertDate(date: Long): String {val df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault())return df.format(date * 1000)}} 当我们使用了两个相同名字的类,我们可以给其中一个指定一个别名,这样我们就不需要写完整的包名了:import com.willkernel.app.kotlindemo.model.Forecast as ModelForecast,从一个forecast list中转换为domain model return list.map { convertForecastItemToDomain(it) }循环这个集合并且返回一个转换后的新的List ForecastMapper 1234567891011121314151617181920212223 public class ForecastDataMapper { fun convertFromDataModel(forecast: ForecastResult): ForecastList { return ForecastList(forecast.city.name, forecast.city.country, convertForecastListToDomain(forecast.list)) } private fun convertForecastListToDomain(list: List<Forecast>) : List<ModelForecast> { return list.map { convertForecastItemToDomain(it) } } private fun convertForecastItemToDomain(forecast: Forecast): ModelForecast { return ModelForecast(convertDate(forecast.dt), forecast.weather[0].description, forecast.temp.max.toInt(), forecast.temp.min.toInt()) } private fun convertDate(date: Long): String { val df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()) return df.format(date * 1000) }} 编写网络请求命令 123456class RequestForecastCommand(val zipCode: String) : Command<ForecastList> { override fun execute(): ForecastList { val forecastRequest = ForecastRequest(zipCode) return ForecastDataMapper().convertFromDataModel(forecastRequest.execute()) }} 修改Adapter,设置数据 123456789101112131415161718192021222324class ForecastListAdapter(val items: ForecastList) : RecyclerView.Adapter<ForecastListAdapter.VH>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { return VH(TextView(parent.context)) } override fun getItemCount(): Int = items.size override fun onBindViewHolder(holder: VH, position: Int) { with(items.dailyForecast[position]) { holder.textView.text = "$date - $description - $high - $low" } } class VH(val textView: TextView) : RecyclerView.ViewHolder(textView)}doAsync { val result = RequestForecastCommand("94043").execute() Log.e(tag, "result " + result.toString()) uiThread { recyclerView.adapter = ForecastListAdapter(result) } } with 函数 with是一个非常有用的函数,它包含在Kotlin的标准库中。它接收一个对象和一个扩展函数作为它的参数,然后使这个对象扩展这个函数。这表示所有我们在括号中编写的代码都是作为对象(第一个参数)的一个扩展函数,我们可以就像作为this一样使用所有它的public方法和属性。当我们针对同一个对象做很多操作的时候这个非常有利于简化代码]]></content>
<categories>
<category>Kotlin</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Kotlin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Kotlin for Android (一)]]></title>
<url>%2F2018%2F03%2F22%2FKotlin-for-Android-%E4%B8%80%2F</url>
<content type="text"><![CDATA[介绍Kotlin 编写代码量少 更加安全:Kotlin编译时期就处理了各种null的情况,避免了执行时异常。如果一个对象可以是null,则我们需要明确地指定它,然后在使用它之前检查它是否是null 它是函数式的:Kotlin是基于面向对象的语言,它使用了很多函数式编程的概念,比如,使用lambda表达式来更方便地解决问题。其中一个很棒的特性就是Collections的处理方式 它可以扩展函数:可以扩展类的更多的特性,甚至我们没有权限去访问这个类中的代码 它是高度互操作性的:你可以继续使用所有的你用Java写的代码和库,因为两个语言之间的互操作性是完美的。可以在一个项目中使用Kotlin和Java两种语言混合编程 特性Expresiveness 可读性 POJO 123456789101112public class Artist {private long id;private String name;private String url;private String mbid;public long getId() {return id;}public void setId(long id) {this.id = id;}··· Kotlin中创建数据类Artist.kt,自动生成所有属性和访问器 1data class Artist(var id: Long, var name: String, var url: String, var mbid: String) 空安全12345678910111213141516171819202122 //编译不通过,非空类不能为null var artist:Artist=null //安全调用操作符? 明确地指定一个对象是否能为空 var artist: Artist? = null // 无法编译, artist可能是null,需要进行处理// artist.hashCode()//在artist!=null时调用artist?.hashCode()// 判空if(artist!=null){ artist.hashCode()}//给定在null时的替代者val name=artist?.name?:"empty"//确保artist不是null的情况下调用,否在抛异常KotlinNullPointerExceptionartist!!.hashCode() 扩展方法1234567val fragment= Fragment()fragment.toast("Hello")fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this@MainActivity, message, duration).show()} Lambda12345textView.setOnClickListener { toast("Kotlin") }private fun toast(text: String) { val fragment = Fragment() fragment.toast(text)} 创建一个项目 Project/build.gradle 123456789101112131415161718192021222324252627buildscript { ext.kotlin_version = '1.2.30' ext.support_version = '26.1.0' ext.anko_version = '0.10.4' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}allprojects { repositories { google() jcenter() }}task clean(type: Delete) { delete rootProject.buildDir} app/build.gradle 123456789101112131415161718192021222324252627282930313233343536373839apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'android { compileSdkVersion 26 defaultConfig { applicationId "com.willkernel.app.kotlindemo" minSdkVersion 23 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } androidExtensions { experimental = true }}dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:26.1.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.anko:anko-common:$anko_version" implementation "org.jetbrains.anko:anko-sqlite:$anko_version" implementation "org.jetbrains.anko:anko-coroutines:$anko_version" implementation 'com.android.support.constraint:constraint-layout:1.0.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'} 转换java到kotlin 导入布局属性import kotlinx.android.synthetic.main.activity_main.* 12textView.setText(R.string.app_name)textView.text = getString(R.string.app_name) 定义类,有一个默认唯一的构造器,如果没有方法,只要名称,变量,可以省略大括号 123456class Person(var name: String, var username: String) { /**构造函数函数体*/ init { }} 任何类继承自Any(类似Object)可以继承其他类,所有类默认不可继承(final),所有只能继承那些声明open或者abstract的类,指定父类中的参数 12open class Animal(name: String)class Person(name: String, surname: String) : Animal(name) 函数 fun 关键字定义函数,没有指定返回值时,返回Unit类似void,但是Unit是一个对象 12345678fun onCreate(savedInstanceState: Bundle?) {}fun add(x: Int, y: Int) : Int {return x + y}fun add(x: Int,y: Int) : Int = x + y 构造方法和函数参数 指定参数默认值 1234567 fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {Toast.makeText(this, message, length).show()}避免重载函数,第二个参数可不传toast("Hello")toast("Hello", Toast.LENGTH_LONG) 三个参数,避免重载函数,string引用模板[$className] $message,${user.name} 1234567private fun toast(message: CharSequence, tag: String = "MainActivity", duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, "[$tag] $message", duration).show() }toast("Hello")toast("Hello", "MyTag")toast("Hello", "MyTag", Toast.LENGTH_SHORT) 创建一个layout 列表使用recyclerview 123456implementation "com.android.support:recyclerview-v7:$support_version" val forcastList = findViewById(R.id.recyclerView) as RecyclerView//通过属性赋值的方式设置LayoutManager(对象实例化没有使用new关键字),而不是通过setterforcastList.layoutManager = LinearLayoutManager(this) Recycler Adapter 12345678910111213class ForcastListAdapter(val items: List<String>) : RecyclerView.Adapter<ForcastListAdapter.VH>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { return VH(TextView(parent.context)) } override fun getItemCount(): Int = items.size override fun onBindViewHolder(holder: VH, position: Int) { holder.textView.text = items[position] } class VH(val textView: TextView) : RecyclerView.ViewHolder(textView) 创建数据,设置Adapter,listOf 创建一个常量的List,它接收一个任何类型的 vararg (可变长的参数),它会自动推断出结果的类型。还有很多其它的函数可以选择,比如 setOf , arrayListOf 或者 hashSetOf 12345678910//val forecastList = findViewById<RecyclerView>(R.id.recyclerView)recyclerView.layoutManager = LinearLayoutManager(this)val items = listOf("Mon 6/23 - Sunny - 31/17", "Tue 6/24 - Foggy - 21/8", "Wed 6/25 - Cloudy - 22/17", "Thurs 6/26 - Rainy - 18/11", "Fri 6/27 - Foggy - 21/10", "Sat 6/28 - TRAPPED IN WEATHERSTATION - 23/18", "Sun 6/29 - Sunny - 20/7")recyclerView.adapter = ForcastListAdapter(items) 变量和属性基本类型 数字类型不会自动转型,做一个明确的类型转换 12345val i:Int=7;val d:Double=i.toDouble()Log.e(tag, d.toString())E/MainActivity: 7.0 字符char的数字值不能直接使用,需要转换为一个数字 12345val c='c'val i2:Int=c.toInt()Log.e(tag,i2.toString()) E/MainActivity: 99 仅适用Int Long的按位运算,包括 1234567shl(bits) – signed shift left (Java's <<)shr(bits) – signed shift right (Java's >>)ushr(bits) – unsigned shift right (Java's >>>)and(bits) – bitwise andor(bits) – bitwise orxor(bits) – bitwise xorinv() – bitwise inversion 示例123456789val FLAG1 = 8Lval FLAG2 = 16L//按位运算,Int Long可用val bitwiseOr = FLAG1 or FLAG2val bitwiseAnd = FLAG1 and FLAG2Log.e(tag, bitwiseOr.toString())Log.e(tag, bitwiseAnd.toString())E/MainActivity: 24E/MainActivity: 0 string字符串的访问 12345678910111213141516//string 可以像数组一样访问val s = "Example"val c1 = s[2]Log.e(tag, c1.toString())for (c in s) { Log.e(tag, "item =$c")}E/MainActivity: aE/MainActivity: item =EE/MainActivity: item =xE/MainActivity: item =aE/MainActivity: item =mE/MainActivity: item =pE/MainActivity: item =lE/MainActivity: item =e 变量 变量可以很简单地定义成可变( var )和不可变( val )的变量,不可变对象也可以说是线程安全的,因为它们无法去改变,也不需要去定义访问控制,因为所有线程访问到的对象都是同一个,尽量使用val,自动从赋值语句判断变量类型1234567891011val actionBar = supportActionBarLog.e(tag, actionBar.toString())val a: Any = 23Log.e(tag, a.toString())val c: Context = thisLog.e(tag, c.toString())E/MainActivity: android.support.v7.app.WindowDecorActionBar@2511c44E/MainActivity: 23E/MainActivity: com.willkernel.app.kotlindemo.MainActivity@b16dc5a 属性 当操作Java代码的时候,Kotlin将允许使用属性的语法去访问在Java文件中定义的getter/setter方法。编译器会直接链接到它原始的getter/setter方法。所以当我们直接访问属性的时候不会有性能开销 在getter和setter中访问这个属性自身的值,它需要创建一个 backingfield 。可以使用 field 这个预留字段来访问,它会被编译器找到正在使用的并自动创建。需要注意的是,如果我们直接调用了属性,那我们会使用setter和getter而不是直接访问这个属性。 backing field 只能在属性访问器内访问12345var nick: String = "" get() = field.toUpperCase() set(value) { field = "Name: $nick" }]]></content>
<categories>
<category>Kotlin</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Kotlin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RxJava2]]></title>
<url>%2F2018%2F03%2F20%2FRxJava2%2F</url>
<content type="text"><![CDATA[RxJava2RxJava是Java VM响应式编程扩展的实现,扩展了观察者模式,通过操作符对数据事件流操作,来编写异步和基于事件的程序,从而不用关心同步,线程安全并发等问题 app/build.gradle123456789101112implementation 'io.reactivex.rxjava2:rxjava:2.1.9'implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'//retrofitimplementation 'com.squareup.retrofit2:retrofit:2.3.0'//Gson converterimplementation 'com.squareup.retrofit2:converter-gson:2.3.0'//RxJava2 Adapterimplementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'//okhttpimplementation 'com.squareup.okhttp3:okhttp:3.8.1'implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0' 事件流向RxJava中的上下游对应被观察者Observable(发布事件)和观察者Observer(接收事件并处理),建立连接observable.subscribe(observer);后才开始发送事件普通方法使用 12345678910111213141516171819202122232425262728293031323334Observable<Integer> observable = Observable.create(emitter -> { emitter.onNext(1); emitter.onNext(2); emitter.onNext(3); emitter.onComplete(); }); Observer<Integer> observer = new Observer<Integer>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(Integer value) { Log.d(TAG, "" + value); } @Override public void onError(Throwable e) { } @Override public void onComplete() { Log.d(TAG, "complete"); } }; observable.subscribe(observer); }2357-2357 D/MainActivity: 12357-2357 D/MainActivity: 22357-2357 D/MainActivity: 32357-2357 D/MainActivity: complete 上游发送onComplete()/onError之后可以继续发送事件,但是下游不处理,onComplete()/onError()唯一且互斥,多个onComplete()不一定会Crash,但是多个onError()会Crash Lambda 链式调用,后面的例子都用这种调用方式 12345678910111213141516171819202122232425Observable.create(emitter -> { emitter.onNext(1); emitter.onNext("2"); emitter.onNext(3); emitter.onComplete();// emitter.onError(new NullPointerException("ERROR"));// emitter.onError(new NullPointerException("ERROR")); }).subscribe(object -> Log.d(TAG, String.valueOf(object)), throwable -> Log.e(TAG, throwable.getMessage()), () -> Log.d(TAG, "onComplete"));Observable.create((ObservableOnSubscribe<Integer>) emitter -> { emitter.onNext(1); emitter.onNext(2); emitter.onNext(3); emitter.onComplete(); }).subscribe(integer -> Log.e(TAG, String.valueOf(integer)), throwable -> Log.e(TAG, throwable.getMessage()), () -> Log.d(TAG, "onComplete"));上述两种打印结果相同2682-2682 D/MainActivity: 12682-2682 D/MainActivity: 22682-2682 D/MainActivity: 32584-2584 E/MainActivity: onComplete dispose()调用处理完事件,处理完即可丢弃,但是上游可以继续发送事件 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051public interface Disposable { /** * Dispose the resource, the operation should be idempotent. */ void dispose(); /** * Returns true if this resource has been disposed. * @return true if this resource has been disposed */ boolean isDisposed();}Observable.create((ObservableOnSubscribe<Integer>) emitter -> { emitter.onNext(1); emitter.onNext(2); emitter.onComplete(); emitter.onNext(3); }).subscribe(new Observer<Integer>() { Disposable mD; @Override public void onSubscribe(Disposable d) { mD = d; Log.e(TAG, "onSubscribe " + d); } @Override public void onNext(Integer integer) { Log.e(TAG, "onNext=" + integer); if (integer == 2) { mD.dispose(); Log.e(TAG, "mD=" + mD.isDisposed()); } } @Override public void onError(Throwable e) { Log.e(TAG, "onError=" + e.getMessage()); } @Override public void onComplete() { Log.e(TAG, "onComplete"); } }); 2800-2800 E/MainActivity: onSubscribe null2800-2800 E/MainActivity: onNext=12800-2800 E/MainActivity: onNext=22800-2800 E/MainActivity: mD=true 线程控制,上游在子线程,下游在主线程 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455Observable.create(emitter -> { Log.e(TAG, Thread.currentThread().getName()); emitter.onNext(1); emitter.onNext("2"); emitter.onNext(3); emitter.onComplete(); }).subscribeOn(Schedulers.io())//指定上游线程,调用多次,只有第一次设置有效 .observeOn(AndroidSchedulers.mainThread())//多次调用下游线程,每次都会切换 .subscribe(object -> { Log.e(TAG, Thread.currentThread().getName()); Log.e(TAG, String.valueOf(object)); }, throwable -> { Log.e(TAG, throwable.getMessage()); }, () -> { Log.e(TAG, "onComplete"); }); 3102-3117 E/MainActivity: RxCachedThreadScheduler-13102-3102 E/MainActivity: main3102-3102 E/MainActivity: 13102-3102 E/MainActivity: main3102-3102 E/MainActivity: 23102-3102 E/MainActivity: main3102-3102 E/MainActivity: 33102-3102 E/MainActivity: onComplete相同例子Observable.create(emitter -> { emitter.onNext(1); emitter.onNext(2); Log.e(TAG, "emitter =" + Thread.currentThread().getName()); }).subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .doOnNext(object -> { Log.e(TAG, "observeOn mainThread=" + Thread.currentThread().getName()); }) .observeOn(Schedulers.io()) .doOnNext(object -> { Log.e(TAG, "observeOn io=" + Thread.currentThread().getName()); }).subscribe(object -> { Log.e(TAG, "subscribe=" + Thread.currentThread().getName()); Log.e(TAG, "subscribe=" + object); }); 3379-3395 E/MainActivity: emitter =RxCachedThreadScheduler-13379-3379 E/MainActivity: observeOn mainThread=main3379-3379 E/MainActivity: observeOn mainThread=main3379-3398 E/MainActivity: observeOn io=RxCachedThreadScheduler-23379-3398 E/MainActivity: subscribe=RxCachedThreadScheduler-23379-3398 E/MainActivity: subscribe=13379-3398 E/MainActivity: observeOn io=RxCachedThreadScheduler-23379-3398 E/MainActivity: subscribe=RxCachedThreadScheduler-23379-3398 E/MainActivity: subscribe=2 网络请求 定义API 1234567public interface Api { @GET Observable<LoginResponse> login(@Body LoginRequest request); @GET Observable<RegisterResponse> register(@Body RegisterRequest request);} OkHttpClient,Retrofit 1234567891011121314151617181920212223private void initOkHttp() { OkHttpClient.Builder builder = new OkHttpClient().newBuilder(); builder.readTimeout(10, TimeUnit.SECONDS); builder.connectTimeout(10, TimeUnit.SECONDS); builder.writeTimeout(10, TimeUnit.SECONDS); builder.retryOnConnectionFailure(true); if (BuildConfig.DEBUG) { HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); builder.addInterceptor(interceptor); } okHttpClient = builder.build(); }private <T> T getApiService(String baseUrl, Class<T> clz) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); return retrofit.create(clz); } 请求网络 12345678910111213141516171819202122232425262728loginApi.login(loginRequest) //在IO线程进行网络请求 .subscribeOn(Schedulers.io()) //回到主线程去处理请求结果 .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<LoginResponse>() { @Override public void onSubscribe(Disposable d) { //Activity退出时结束事件 CompositeDisposable.clear() compositeDisposable.add(d); } @Override public void onNext(LoginResponse value) { Log.e(TAG, "value=" + value); } @Override public void onError(Throwable e) { Log.e(TAG, "onError" + e.getMessage()); } @Override public void onComplete() { Log.e(TAG, "onComplete"); Toast.makeText(context, "onComplete", Toast.LENGTH_LONG).show(); } }); 读写数据库 123456789101112131415161718192021222324Observable.create((ObservableOnSubscribe<List<String>>) e -> {// Cursor cursor = null;// try {// cursor = getReadableDatabase().rawQuery("select * from " + TABLE_NAME, new String[]{});// List<Record> result = new ArrayList<>();// while (cursor.moveToNext()) {// result.add(Db.Record.read(cursor));// }// emitter.onNext(result);// emitter.onComplete();// } finally {// if (cursor != null) {// cursor.close();// }// } List<String> result = new ArrayList<>(); result.add("1"); result.add("2"); result.add("3"); e.onNext(result); e.onComplete(); }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(strings -> Log.e(TAG, strings.toString())); map操作符,可以将上游发来的事件转换为任意的类型, 可以是一个Object, 也可以是一个集合 1234567891011121314Observable.create((ObservableOnSubscribe<Integer>) e -> { e.onNext(1); e.onNext(2); e.onNext(3); e.onComplete(); }).map(integer -> "result " + integer) .subscribe(string -> Log.e(TAG, "map " + string), throwable -> Log.e(TAG, throwable.getMessage()), () -> Log.e(TAG, "onComplete")); E/MainActivity: map result 1E/MainActivity: map result 2E/MainActivity: map result 3E/MainActivity: onComplete FlatMap将一个发送事件的上游Observable变换为多个发送事件的Observables,然后将它们发送的事件合并成一个单独的Observable.flatMap并不保证事件的顺序,如果需要保证顺序则需要使用concatMap,下面例子flatmap可以改为concatMap 12345678910111213141516171819202122232425Observable.create((ObservableOnSubscribe<Integer>) emitter -> { emitter.onNext(1); emitter.onNext(2); emitter.onNext(3); emitter.onComplete(); }).flatMap(integer -> { List<String> list = new ArrayList<>(); for (int i = 0; i < 3; i++) { list.add("I am value " + integer); } return Observable.fromIterable(list).delay(1, TimeUnit.SECONDS); }).subscribe(string -> Log.e(TAG, string), throwable -> Log.e(TAG, throwable.getMessage()), () -> Log.e(TAG, "onComplete")); E/MainActivity: I am value 1E/MainActivity: I am value 1E/MainActivity: I am value 1E/MainActivity: I am value 2E/MainActivity: I am value 3E/MainActivity: I am value 2E/MainActivity: I am value 2E/MainActivity: I am value 3E/MainActivity: I am value 3E/MainActivity: onComplete 嵌套请求,注册登陆 12345678910loginApi.register(registerRequest)//发起注册请求 .subscribeOn(Schedulers.io())//io线程注册 .observeOn(AndroidSchedulers.mainThread())//注册结果在主线程 .doOnNext(registerResponse -> Log.e(TAG, "registerResponse"))//注册结果 .subscribeOn(Schedulers.io())//在io线程登陆 .flatMap(mapper -> loginApi.login(new LoginRequest()))//登陆 .observeOn(AndroidSchedulers.mainThread())//登陆后回到主线程 .subscribe(loginResponse -> Log.e(TAG, "login success"), throwable -> Log.e(TAG, "login failure " + throwable.getMessage()), () -> Log.e(TAG, "onComplete")); map与flatmap 区别,map在返回对象集合时,subscribe中需要循环迭代取出元素处理,而flatmap将集合通过Observable.fromIterable迭代获取对象变换为多个Observable发送到下游分别处理,效率更高,在多个同步任务中,推荐使用flatmap 当发送非Observable对象时,使用map,返回的是普通对象或集合 当发送的是Observable对象时,使用flatmap,返回的是Observable<T>1234567891011121314E/MainActivity: map [I am value 1, I am value 1, I am value 1]E/MainActivity: map [I am value 2, I am value 2, I am value 2]E/MainActivity: map [I am value 3, I am value 3, I am value 3]E/MainActivity: onCompleteE/MainActivity: I am value 1E/MainActivity: I am value 1E/MainActivity: I am value 1E/MainActivity: I am value 2E/MainActivity: I am value 2E/MainActivity: I am value 2E/MainActivity: I am value 3E/MainActivity: I am value 3E/MainActivity: I am value 3E/MainActivity: onComplete zip操作符,将多个Observable事件按照顺序整合在一起,发送组合在一起的事件,Observable o1 发送4个数据,Observable o2 发送3个数据,最后整合按照个数最少的进行整合 顺序执行,在同一个线程 12345678910111213141516171819202122232425262728293031323334353637Observable<Integer> o1 = Observable.create(emitter -> { emitter.onNext(1); Log.e(TAG,"onNext 1"); emitter.onNext(2); Log.e(TAG,"onNext 2"); emitter.onNext(3); Log.e(TAG,"onNext 3"); emitter.onNext(4); Log.e(TAG,"onNext 4"); emitter.onComplete(); }); Observable<String> o2 = Observable.create(emitter -> { emitter.onNext("A"); Log.e(TAG,"onNext A"); emitter.onNext("B"); Log.e(TAG,"onNext B"); emitter.onNext("C"); Log.e(TAG,"onNext C"); emitter.onComplete(); }); Observable.zip(o1, o2, (integer, string) -> integer + string) .subscribe(string -> Log.e(TAG, string), throwable -> Log.e(TAG, throwable.getMessage()), () -> Log.e(TAG, "onComplete")); E/MainActivity: onNext 1E/MainActivity: onNext 2E/MainActivity: onNext 3E/MainActivity: onNext 4E/MainActivity: 1AE/MainActivity: onNext AE/MainActivity: 2BE/MainActivity: onNext BE/MainActivity: 3CE/MainActivity: onNext CE/MainActivity: onComplete 不在同一个线程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 Observable<Integer> observable1 = Observable.create((ObservableOnSubscribe<Integer>) emitter -> { Log.d(TAG, "emit 1"); emitter.onNext(1); Thread.sleep(1000); Log.d(TAG, "emit 2"); emitter.onNext(2); Thread.sleep(1000); Log.d(TAG, "emit 3"); emitter.onNext(3); Log.d(TAG, "emit 4"); emitter.onNext(4); Log.d(TAG, "emit complete1"); emitter.onComplete(); }).subscribeOn(Schedulers.io()); Observable<String> observable2 = Observable.create((ObservableOnSubscribe<String>) emitter -> { Log.d(TAG, "emit A"); emitter.onNext("A"); Thread.sleep(1000); Log.d(TAG, "emit B"); emitter.onNext("B"); Thread.sleep(1000); Log.d(TAG, "emit C"); emitter.onNext("C"); Thread.sleep(1000); Log.d(TAG, "emit complete2"); emitter.onComplete(); }).subscribeOn(Schedulers.io()); Observable.zip(observable1, observable2, (integer, string) -> integer + string) .subscribe(string -> Log.e(TAG, string), throwable -> Log.e(TAG, throwable.getMessage()), () -> Log.e(TAG, "onComplete")); D/MainActivity: emit 1D/MainActivity: emit AE/MainActivity: 1AD/MainActivity: emit 2D/MainActivity: emit BE/MainActivity: 2BD/MainActivity: emit 3D/MainActivity: emit 4D/MainActivity: emit complete1D/MainActivity: emit CE/MainActivity: 3CD/MainActivity: emit complete2E/MainActivity: onComplete 需要在获取到用户基本信息和其他信息之后显示界面,使用zip 123456789Observable<UserBaseInfoResponse> observable1 = userApi.getUserBaseInfo(baseInfoRequest).subscribeOn(Schedulers.io());Observable<UserExtraInfoResponse> observable2 = userApi.getUserExtraInfo(extraInfoRequest).subscribeOn(Schedulers.io());Observable.zip(observable1, observable2, UserInfo::new) .observeOn(AndroidSchedulers.mainThread()) .subscribe(userInfo -> { }, throwable -> { }); Backpressure 控制发送事件的速率,同一线程下,上游发送的事件,需要等待下游处理完再发送下个事件,在不同的线程,就不用等下游是否处理完,会造成上游不断发送事件,造成OOM 过滤 12345678Observable.create((ObservableOnSubscribe<Integer>) e -> { for (int i = 0; ; i++) { e.onNext(i); } }).subscribeOn(Schedulers.io()) .filter(integer -> integer % 100 == 0) .observeOn(AndroidSchedulers.mainThread()) .subscribe(integer -> Log.d(TAG, String.valueOf(integer))); 2s采样 12345678Observable.create((ObservableOnSubscribe<Integer>) e -> { for (int i = 0; ; i++) { e.onNext(i); } }).subscribeOn(Schedulers.io()) .sample(2, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(integer -> Log.d(TAG, "" + integer)); 延时发送 12345678Observable.create((ObservableOnSubscribe<Integer>) emitter -> { for (int i = 0; ; i++) { emitter.onNext(i); Thread.sleep(2000); //每次发送完事件延时2秒 } }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(integer -> Log.d(TAG, "" + integer)); Flowable,上游是Flowable,下游是Subscriber,两者之间通过subscribe()连接,当第二个参数为BackpressureStrategy.ERROR,上下游不均衡时抛出MissingBackpressureException 12345678910111213141516171819202122232425262728293031323334Flowable.create((FlowableOnSubscribe<Integer>) emitter -> { emitter.onNext(1); emitter.onNext(2); emitter.onNext(3); emitter.onComplete(); }, BackpressureStrategy.ERROR) .subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { Log.d(TAG, "onSubscribe "); s.request(3); } @Override public void onNext(Integer integer) { Log.d(TAG, "" + integer); } @Override public void onError(Throwable t) { Log.d(TAG, t.getMessage()); } @Override public void onComplete() { Log.e(TAG, "onComplete"); } });D/MainActivity: onSubscribe D/MainActivity: 1D/MainActivity: 2D/MainActivity: 3E/MainActivity: onComplete Subscription 同Disposable类似,可以调用Subscription.cancel()取消事件的执行,增加一个void request(long n), 如果下游没有调用request, 上游就认为下游没有处理事件的能力,而这又是一个同步的订阅,会造成上游等待处理界面卡死,直接抛异常.所以下游调用request(Long.MAX_VALUE)或者根据上游发送事件的数量request(3) 当上下游不在同一个线程,因为在Flowable里默认有一个大小为128的水缸, 当上下游工作在不同的线程中时,上游就会先把事件发送到这个水缸中,因此, 下游虽然没有调用request,但是上游在水缸中保存着这些事件, 只有当下游调用request时, 才从水缸里取出事件发给下游. 1234567891011121314151617181920212223242526272829Flowable.create((FlowableOnSubscribe<Integer>) e -> { for (int i = 0; i < 129; i++) { Log.d(TAG, "emit " + i); e.onNext(i); } }, BackpressureStrategy.ERROR) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { Log.e(TAG, "onSubscribe"); } @Override public void onNext(Integer integer) { Log.e(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.e(TAG, "onError: " + t); } @Override public void onComplete() { Log.e(TAG, "onComplete "); } }); 更改背压策略BackpressureStrategy.BUFFER,事件发送处理类似Observable,Observer,但是当无限发送事件,内存会OOM,需要采用其他策略 1234567891011121314151617181920212223242526272829Flowable.create((FlowableOnSubscribe<Integer>) e -> { for (int i = 0; i < 1000; i++) { Log.d(TAG, "emit " + i); e.onNext(i); } }, BackpressureStrategy.BUFFER) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { Log.e(TAG, "onSubscribe"); } @Override public void onNext(Integer integer) { Log.e(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.e(TAG, "onError: " + t); } @Override public void onComplete() { Log.e(TAG, "onComplete "); } }); 更改为BackpressureStrategy.DROP丢弃存不下的数据 123456789101112131415161718192021222324252627282930313233343536373839Flowable.create((FlowableOnSubscribe<Integer>) e -> { for (int i = 0; ; i++) { e.onNext(i); } }, BackpressureStrategy.DROP) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Integer>() { Subscription mSubscription; Runnable runnable = new Runnable() { @Override public void run() { mSubscription.request(128); text.postDelayed(runnable, 2000); } }; @Override public void onSubscribe(Subscription s) { Log.d(TAG, "onSubscribe"); mSubscription = s; text.postDelayed(runnable, 2000); } @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.w(TAG, "onError: ", t); } @Override public void onComplete() { Log.d(TAG, "onComplete"); } }); 更改为BackpressureStrategy.LATEST保存最新的数据 123456789101112131415161718192021222324252627282930313233343536373839Flowable.create((FlowableOnSubscribe<Integer>) e -> { for (int i = 0; ; i++) { e.onNext(i); } }, BackpressureStrategy.LATEST) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Integer>() { Subscription mSubscription; Runnable runnable = new Runnable() { @Override public void run() { mSubscription.request(128); text.postDelayed(runnable, 2000); } }; @Override public void onSubscribe(Subscription s) { Log.d(TAG, "onSubscribe"); mSubscription = s; text.postDelayed(runnable, 2000); } @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.w(TAG, "onError: ", t); } @Override public void onComplete() { Log.d(TAG, "onComplete"); } }); interval操作符发送Long型的数据,下面的例子,每隔一毫秒发送事件,下游一秒处理一个事件,直接发送会抛异常,解决办法加背压策略 onBackpressureBuffer() onBackpressureDrop() onBackpressureLatest()123456789101112131415161718192021222324252627282930 Flowable.interval(1, TimeUnit.MICROSECONDS)// .onBackpressureDrop() .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Long>() { @Override public void onSubscribe(Subscription s) { Log.d(TAG, "onSubscribe"); s.request(Long.MAX_VALUE); } @Override public void onNext(Long aLong) { Log.e(TAG, "onNext: " + aLong); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onError(Throwable t) { Log.e(TAG, "onError: ", t); } @Override public void onComplete() { Log.d(TAG, "onComplete"); } }); FlowableEmiiter 源码 12345678910111213141516171819public interface FlowableEmitter<T> extends Emitter<T> { void setDisposable(@Nullable Disposable s); void setCancellable(@Nullable Cancellable c); /** * The current outstanding request amount. * <p>This method is thread-safe. * @return the current outstanding request amount */ long requested(); boolean isCancelled(); @NonNull FlowableEmitter<T> serialize(); @Experimental boolean tryOnError(@NonNull Throwable t);} requested为当前下游处理能力 123456789101112131415161718192021222324252627Flowable.create((FlowableOnSubscribe<Integer>) e -> Log.e(TAG, "current request=" + e.requested()), BackpressureStrategy.ERROR) .subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { Log.e(TAG, "onSubscribe"); s.request(10); s.request(100);// current request 110; } @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.e(TAG, "onError: ", t); } @Override public void onComplete() { Log.e(TAG, "onComplete"); } }); 下游处理完一个事件,requested-1,complete和error事件不会消耗requested,当requested=0,上游继续发送事件,会抛异常 123456789101112131415161718192021222324252627282930313233343536373839Flowable.create((FlowableOnSubscribe<Integer>) e -> { Log.e(TAG, "current request=" + e.requested()); Log.e(TAG, "emitter 1"); e.onNext(1); Log.e(TAG, "current request=" + e.requested()); Log.e(TAG, "emitter 2"); e.onNext(2); Log.e(TAG, "current request=" + e.requested()); Log.e(TAG, "emitter 3"); e.onNext(3); Log.e(TAG, "current request=" + e.requested()); e.onComplete(); }, BackpressureStrategy.ERROR) .subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { Log.e(TAG, "onSubscribe"); s.request(10); } @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.e(TAG, "onError: ", t); } @Override public void onComplete() { Log.e(TAG, "onComplete"); } }); 当上下游工作在不同的线程里时,每一个线程里都有一个requested,而我们调用request(1000)时,实际上改变的是下游主线程中的requested,而上游中的requested的值是由RxJava内部调用request(n)去设置的,这个调用会在合适的时候自动触发 1234567891011121314151617181920212223242526272829Flowable .create((FlowableOnSubscribe<Integer>) emitter -> { Log.d(TAG, "current requested: " + emitter.requested());//还是128 }, BackpressureStrategy.ERROR) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { Log.d(TAG, "onSubscribe"); s.request(1000); } @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.w(TAG, "onError: ", t); } @Override public void onComplete() { Log.d(TAG, "onComplete"); } }); 上游发送完128个事件,下游处理完96个事件后,上游继续发送128个事件,下游继续处理 1234567891011121314151617181920212223242526272829303132333435363738394041424344Flowable.create((FlowableOnSubscribe<Integer>) emitter -> { Log.d(TAG, "First requested = " + emitter.requested()); boolean flag; for (int i = 0; ; i++) { flag = false; while (emitter.requested() == 0) { if (!flag) { Log.d(TAG, "Oh no! I can't emit value!"); flag = true; } } emitter.onNext(i); Log.d(TAG, "emit " + i + " , requested = " + emitter.requested()); } }, BackpressureStrategy.ERROR) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(final Subscription s) { Log.d(TAG, "onSubscribe"); text.postDelayed(() -> { //在处理完96个事件后,上游可以继续发送emit 128-223 共96个 //95s时,上游不发送事件 s.request(95); }, 1000); } @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.w(TAG, "onError: ", t); } @Override public void onComplete() { Log.d(TAG, "onComplete"); } }); 一行一行读取文件 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263Flowable.create((FlowableOnSubscribe<String>) e -> { try {// URL url=getClass().getResource("test.txt");// Log.e(TAG,"url="+url);// FileReader reader =new FileReader(url.getFile());// FileReader reader =new FileReader("file:///android_asset/test.txt"); InputStream inputStream = getResources().getAssets().open("test.txt"); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str; while ((str = bufferedReader.readLine()) != null && !e.isCancelled()) { while (e.requested() == 0) { if (e.isCancelled()) break; } e.onNext(str); } bufferedReader.close(); inputStreamReader.close(); inputStream.close(); e.onComplete(); } catch (IOException e1) { e1.printStackTrace(); } }, BackpressureStrategy.ERROR) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.newThread()) .subscribe(new Subscriber<String>() { Subscription mSubscription; @Override public void onSubscribe(Subscription s) { mSubscription = s; s.request(1); } @Override public void onNext(String s) { Log.e(TAG, "onNext=" + s); try { Thread.sleep(2000); mSubscription.request(1); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onError(Throwable t) { System.out.println(t); } @Override public void onComplete() { } }); E/MainActivity: onNext=AAAAAAAAAAAAE/MainActivity: onNext=BBBBBBBBE/MainActivity: onNext=CCCCCCCE/MainActivity: onNext=DDDDDE/MainActivity: onNext=EEE/MainActivity: onNext=F RxJava操作符(待更…) 参考给初学者的RxJava2.0教程RxJava2操作符系列RxJava2-Android-SamplesRxJava学习]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>RxJava2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Data Binding]]></title>
<url>%2F2018%2F03%2F16%2FData-Binding%2F</url>
<content type="text"><![CDATA[Data Binding Library数据绑定库编写声明式布局,尽量减少绑定应用程序逻辑和布局所需的代码,减少布局绑定相关代码 build environment 123456android{... dataBinding { enabled = true }} Data Binding Compiler V2 12android.databinding.enableV2=true向后不兼容 Data Binding 布局文件 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="java.util.List" /> <import type="android.view.View" /> <import type="com.willkernel.www.databindingdemo.User" /> <import type="com.willkernel.www.databindingdemo.DoSomething" /> <!--<variable--> <!--name="view"--> <!--type="View" />--> <variable name="user" type="User" /> <variable name="userClick" type="com.willkernel.www.databindingdemo.MainActivity.UserClick" /> <variable name="ds" type="DoSomething" /> </data> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{userClick::onUserClick}" android:text="@{user.firstName}" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{() -> userClick.onUserClick(user)}" android:text="@{user.firstName}" /> <!--android:onClick="@{userClick::onUserClick}"--> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{(v) -> userClick.onUserClick(v,user)}" android:text="@{user.firstName}" /> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:onCheckedChanged="@{(cb,isChecked) -> userClick.onCheckedChanged(user,isChecked)}" android:text="@{user.lastName}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onLongClick="@{(v)->userClick.onLongClick(v,user)}" android:text="@{user.lastName}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:visibility="@{user.isAdult ? View.VISIBLE:View.INVISIBLE}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{DoSomething.capital(user.firstName)}" /> </LinearLayout> </android.support.v4.widget.NestedScrollView></layout> data标签下的variable描述将会在布局中使用的属性 1<variable name="user" type="com.example.User"/> 使用”@{}” 语法获取属性 123<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/> 三种情况databinding都可以获取属性 123456789101. public final String firstName;2. private final String firstName; public String getFirstName() { return this.firstName; }3. private final String firstName; public String firstName() { return this.firstName; } 数据绑定,默认基于布局文件名称生成绑定类,将其转换为Pascal实例并添加Binding后缀,例如布局文件activity_main.xml,生成的类为MainActivityBinding,持有布局所有属性的绑定方法 12345678910111213141516 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User(); user.firstName = "firstName"; user.lastName = "lastName"; user.isAdult = true; binding.setUser(user); binding.setUserClick(new UserClick());}也可以这样获取ViewActivityMainBinding binding =ActivityMainBinding.inflate(getLayoutInflater());MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false); ListView,RecyclerView适配器中绑定 123ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);//orListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false); 如果通过其他方式填充的布局,需要调用bind方法 1MyLayoutBinding binding =MyLayoutBinding.bind(viewRoot); 另外的方式 123ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId); 布局中的views将没有设置id的会生成private final ID,设置id的会生成public final ID ,比findViewById速度更快 生成的方法 variable 12345678910111213<data> <import type="android.graphics.drawable.Drawable"/> <variable name="user" type="com.example.User"/> <variable name="image" type="Drawable"/> <variable name="note" type="String"/></data>public abstract com.example.User getUser();public abstract void setUser(com.example.User user);public abstract Drawable getImage();public abstract void setImage(Drawable image);public abstract String getNote();public abstract void setNote(String note); ViewStubs因为在显示后会从view层级中消失,所以view的binding对象也会被回收 12345678910111213@NonNullpublic final android.databinding.ViewStubProxy viewStub;binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { Log.e(TAG, "inflated=" + inflated + " stub=" + stub); }});if (!binding.viewStub.isInflated()) { binding.viewStub.getViewStub().inflate();} 事件处理两种方式 方法引用 1234567891011121314151617181920 public class MyHandlers { public void onClickFriend(View view) { ... }}<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="handlers" type="com.example.MyHandlers"/> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:onClick="@{handlers::onClickFriend}"/> </LinearLayout></layout> 监听绑定 123456789101112131415 public class Presenter { public void onSaveClick(Task task){}}绑定点击事件到类 <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="task" type="com.android.example.Task" /> <variable name="presenter" type="com.android.example.Presenter" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.onSaveClick(task)}" /> </LinearLayout> </layout> “@{() -> presenter.onSaveClick(task)}”可以忽略所有参数,也可以写上参数 1android:onClick="@{(view) -> presenter.onSaveClick(task)}" 如果类中有参数View 12345 public class Presenter { public void onSaveClick(View view, Task task){}}绑定点击事件android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}" 可以使用lambda表达式添加更多参数 123456 public class Presenter { public void onCompletedChanged(Task task, boolean completed){}} <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" /> 如果类中的方法返回值不是void,点击事件的绑定表达式返回值应该和方法一样,如果返回值因为空对象不能判断,会返回对象的默认值 1234 public class Presenter { public boolean onLongClick(View view, Task task){}} android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}" 可以使用void作为三元运算符的标记 1android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}" 避免复杂监听,有些默认的点击事件处理的实现 12345SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClickZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomOut ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut Layout Details data元素标签下,可以导入多个类 123<data> <import type="android.view.View"></data> binding表达式中可以使用View 12345<TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/> 导入类名冲突可以使用别名 123<import type="android.view.View"/><import type="com.example.real.estate.View" alias="Vista"/> 导入类可以设置变量 123456<data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List&lt;User&gt;"/></data> 引用静态变量和方法 123456789<data> <import type="com.example.MyStringUtils"/> <variable name="user" type="com.example.User"/></data>…<TextView android:text="@{MyStringUtils.capitalize(user.lastName)}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> 如果variable实现了android.databinding.Observable或者observable collection,应该在反映在type中,当没有实现上述接口时,变量将不会被观察Observer 当横竖屏不同布局时,变量会整合,不同布局不要有命名冲突 binding 类的主要方法,其中context是来自rootview的contextbinding.getRoot().getContext() 重命名Binding对象 12<data class="MainAty"> MainAty binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 设置Binding包名 1234当前应用包名,这种方式会导致Binding对view id绑定失效 <data class=".MainAty"> 重新设置完整的包名 <data class="com.main.MainAty"> include 绑定变量 12<include layout="@layout/contact" bind:user="@{user}"/> include布局元素不能作为merge的子元素 表达式语法 1234exapmples:android:text="@{String.valueOf(index + 1)}"android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"android:transitionName='@{"image_" + id}' 非空判断?? 123android:text="@{user.displayName ?? user.lastName}"等价于android:text="@{user.displayName != null ? user.displayName : user.lastName}" 规避空指针异常,当variable为空时,属性值会显示相应的默认值 集合 123456789101112131415161718192021222324\<import type="java.util.List" /> <import type="android.util.SparseArray" /> <import type="java.util.Map" /> <variable name="list" type="List&lt;String&gt;" /> <variable name="sparse" type="SparseArray&lt;String&gt;" /> <variable name="map" type="Map&lt;String,String&gt;" /> <variable name="index" type="int" /> <variable name="key" type="String" />android:text="@{list[index]}"android:text="@{sparse[index]}"android:text="@{map[key]}" 字符串引用 123android:text='@{map["firstName"]}'android:text="@{map[`firstName`}"android:text="@{map['firstName']}" 表达式中的资源引用 数据对象 数据自动更新通知机制 123Observable objectsObservable fieldsObservable collections Observable Objects 继承自BaseObservable 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 public class BaseObservable implements Observable { private transient PropertyChangeRegistry mCallbacks; public BaseObservable() { } @Override public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) { synchronized (this) { if (mCallbacks == null) { mCallbacks = new PropertyChangeRegistry(); } } mCallbacks.add(callback); } @Override public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) { synchronized (this) { if (mCallbacks == null) { return; } } mCallbacks.remove(callback); } /** * Notifies listeners that all properties of this instance have changed. */ public void notifyChange() { synchronized (this) { if (mCallbacks == null) { return; } } mCallbacks.notifyCallbacks(this, 0, null); } /** * Notifies listeners that a specific property has changed. The getter for the property * that changes should be marked with {@link Bindable} to generate a field in * <code>BR</code> to be used as <code>fieldId</code>. * * @param fieldId The generated BR id for the Bindable field. */ public void notifyPropertyChanged(int fieldId) { synchronized (this) { if (mCallbacks == null) { return; } } mCallbacks.notifyCallbacks(this, fieldId, null); }} 在getter()添加@Bindable注解,在setter()中添加notifyPropertyChanged(BR.lastName); 12345678910111213141516171819202122232425262728293031323334 private static class User extends BaseObservable { private String firstName; private String lastName; @Bindable public String getFirstName() { return this.firstName; } @Bindable public String getLastName() { return this.lastName; } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); }}上面user例子,实现自动刷新UI@Bindablepublic boolean isAdult() { return isAdult;}public void setAdult(boolean adult) { isAdult = adult; notifyPropertyChanged(BR.adult); }user.isAdult = !user.isAdult;binding.setUser(user); Observable Fields 不需要写setter() getter(),需要初始化new Observable***() 12public class ObservableParcelable<T extends Parcelable> extends ObservableField<T> implements Parcelable, Serializable 实例 12345678910 private static class User { public final ObservableField<String> firstName = new ObservableField<>(); public final ObservableField<String> lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt();}user.firstName.set("Google");int age = user.age.get(); Observable Collections ObservableArrayMap 1234567ObservableArrayMap<String, Object> observableMap = new ObservableArrayMap<>();observableMap.put("firstName", "Google");observableMap.put("lastName", "Inc.");observableMap.put("age", 17);binding.setObservableMap(observableMap);android:text='@{String.valueOf(observableMap["age"])}' ObservableArrayList 1234567ObservableArrayList<Object> list = new ObservableArrayList<>();list.add("Google");list.add("Inc.");list.add(17);binding.setObservableList(list); android:text="@{String.valueOf(observableList[index])}" Advanced Binding 高级绑定 动态变量RecyclerView.Adapter中在onBindViewHolder(VH, int)设置值,其中BindingHolder有一个getBinding()方法 12345public void onBindViewHolder(BindingHolder holder, int position) { final T item = mItems.get(position); holder.getBinding().setVariable(BR.item, item); holder.getBinding().executePendingBindings();} 立即绑定当变量或观察者发生变化时,绑定会在下一帧绘制时发生改变,然而有时候需要强制重新绑定 1android.databinding.ViewDataBinding.executePendingBindings() 后台线程 可以在后台线程改变数据model,只要不是集合,数据绑定本地化每个变量,规避线程并发问题 属性设置 自动设置属性布局文件 123456<ImageView android:layout_width="200dp" android:layout_height="200dp" android:scaleType="centerCrop" app:imageUrl="@{user.imageUrl}" app:placeHolder="@{@drawable/place}" /> 可以在任意类中绑定属性值设置方法,通常以模块,view自定义命名的Bindings类 12345678@BindingAdapter(value = {"imageUrl", "placeHolder"}, requireAll = false)public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (!TextUtils.isEmpty(url)){Glide.with(imageView.getContext()).load(url).into(imageView).onLoadStarted(placeHolder); } else { imageView.setImageDrawable(placeHolder); }} 重命名setters,Android 实现了很多BindingAdapters,查看TextViewBinding 123456789101112@BindingMethods({ @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"), @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"), @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"), @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"), @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"), @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"), @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"), @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),}) 自定义设置方法 有下面的方法setPadding(int left, int top, int right, int bottom)没有setPaddingLeft(),但是有android:paddingLeft属性 123456789@BindingAdapter("android:paddingLeft")public static void setPaddingLeft(View view, int oldPadding, int newPadding) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }} 一个抽象方法的接口或抽象类的事件处理,注意,此方法必须在自定义View中编写,下面的是官方文档代码示例 123456789101112 @BindingAdapter("android:onLayoutChange")public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue); } if (newValue != null) { view.addOnLayoutChangeListener(newValue); } }} 如果是多个抽象方法的事件处理 123456789 @TargetApi(VERSION_CODES.HONEYCOMB_MR1)public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v);}@TargetApi(VERSION_CODES.HONEYCOMB_MR1)public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v);} 1234567891011121314151617181920212223242526272829303132333435363738394041424344 @BindingAdapter("android:onViewAttachedToWindow")public static void setListener(View view, OnViewAttachedToWindow attached) { setListener(view, null, attached);}@BindingAdapter("android:onViewDetachedFromWindow")public static void setListener(View view, OnViewDetachedFromWindow detached) { setListener(view, detached, null);}@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})public static void setListener(View view, final OnViewDetachedFromWindow detach, final OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { final OnAttachStateChangeListener newListener; if (detach == null && attach == null) { newListener = null; } else { newListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (attach != null) { attach.onViewAttachedToWindow(v); } } @Override public void onViewDetachedFromWindow(View v) { if (detach != null) { detach.onViewDetachedFromWindow(v); } } }; } final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } }} 自定义View实现自定义监听事件处理12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 public class ColorPicker extends View { private int color; public ColorPicker(Context context) { super(context); } public ColorPicker(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public ColorPicker(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public int getColor() { return color; } public void setColor(int color) { this.color = color; invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(getResources().getColor(color)); } public void removeListener(OnColorChangeListener oldListener) { oldListener.onColorChange(this, color); Log.e("color", "remove"); } public void addListener(OnColorChangeListener newListener) { newListener.onColorChange(this, color); Log.e("color", "add"); } public interface OnColorChangeListener { void onColorChange(ColorPicker view, int newColor); } @BindingAdapter("onColorChange") public static void setColorChangeListener(ColorPicker view, ColorPicker.OnColorChangeListener oldListener, ColorPicker.OnColorChangeListener newListener) { Log.e("oldListener", "="+oldListener); Log.e("newListener", "="+newListener); if (oldListener != null) { view.removeListener(oldListener); } if (newListener != null) { view.addListener(newListener); } }} 布局引用 <com.willkernel.www.databindingdemo.ColorPicker android:id="@+id/colorPicker" android:layout_width="100dp" android:layout_height="100dp" app:color="@{color}" app:onColorChange="@{(v,color)->main.colorChanged(v,color)}" /> MainActivity handler中public void colorChanged(int color) { Log.e(TAG, "colorChanged=" + color); } binding.setColor(R.color.colorAccent); 转换 对象类型转换,需要使用public static修饰方法 12345@BindingConversionpublic static String convertDateToText(Date date){ DateFormat dateFormat=DateFormat.getDateInstance(); return dateFormat.format(date);} 自定义转换 颜色值int color转换为ColorDrawable,转换发生在setter level,不允许两种类型混合使用,int和drawable在同一个三元运算符中 123456789<View android:background="@{isError ? @color/red : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>@BindingConversionpublic static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color);}]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Dagger]]></title>
<url>%2F2018%2F03%2F13%2FDagger%2F</url>
<content type="text"><![CDATA[Dagger依赖注入(Dependency Injection),简称DI,又叫控制反转(Inversion of Control),简称IOC当一个类的实例需要另一个类的实例,在传统的设计中,通常由调用者来创建被调用者的实例,然而依赖注入的方式,创建被调用者不再由调用者创建实例,创建被调用者的实例的工作由IOC容器来完成,然后注入到调用者。因此也被称为依赖注入 API12345678910111213141516171819202122232425262728293031public @interface Component { Class<?>[] modules() default {}; Class<?>[] dependencies() default {}; @Target(TYPE) @Documented @interface Builder {}}public @interface Subcomponent { Class<?>[] modules() default {}; @Target(TYPE) @Documented @interface Builder {}}public @interface Module { Class<?>[] includes() default {}; @Beta Class<?>[] subcomponents() default {};}public @interface Provides {}public @interface MapKey { boolean unwrapValue() default true;}public interface Lazy<T> { T get();} @Inject声明依赖注入构造方法,自动请求参数并调用构造方法 12345678910 class Thermosiphon implements Pump { private final Heater heater; @Inject Thermosiphon(Heater heater) { this.heater = heater; } ...} @Inject fields 实例化成员变量如果你的类有@Inject成员变量,但是没有@Inject构造方法,Dagger会注入这些变量,但是不会创建新的对象。所有添加@Inject无参的构造函数让Dagger也可以创建对象,也可以@Inject method(),当然Field,Constructor是首选。 12345 class CoffeeMaker { @Inject Heater heater; @Inject Pump pump; ...} @Inject不足 1234类中只能包含一个@Inject constructor接口,抽象类是没有构造方法的第三方库提供的类,它们的构造方法不能被注解有些类需要灵活选择初始化的配置,而不是使用一个单一的构造方法 @Provides 可以满足上面的依赖性,但是必须属于一个Module,有一个惯例方法前缀加provide,类后缀加Module 12345678910 @Moduleclass DripCoffeeModule { @Provides static Heater provideHeater() { return new ElectricHeater(); } @Provides static Pump providePump(Thermosiphon pump) { return pump; }} @Component 在接口中,传入Module,完成接口和类的注入,将CoffeeShop 通过DripCoffeeModule注入到MainActivity中@Component中使用modules,表明该Component在哪些注入的Module中查找依赖@Component中使用dependencie,表明该Component在哪些注入的Component中查找依赖添加注入方法,一般使用inject可以声明方法,提供注入的实例 123456789101112131415161718 @Component(modules = DripCoffeeModule.class)interface CoffeeShop { CoffeeMaker maker();}CoffeeShop coffeeShop = DaggerCoffeeShop.builder() .dripCoffeeModule(new DripCoffeeModule()) .build();//如果@Component不是在类的上方,生成组件时需要添加下划线class Foo { static class Bar { @Component interface BazComponent {} }}DaggerFoo_Bar_BazComponent 如果所有的依赖关系都可以在不需要实例对象的情况下创建,那么可以使用builder(),也可以使用crete()创建实例,对于所有的@Provides方法都是静态的Module,可以不需要考虑builder 1CoffeeShop coffeeShop = DaggerCoffeeShop.create(); 针对在MainActivity中的依赖注入 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162 public class Car { @Inject public Car() { } public String show() { return String.valueOf(1); }} @Componentpublic interface MainActivityComponent { void inject(MainActivity activity);}public class MainActivity extends AppCompatActivity { @Inject Car car; protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 先编译一次crtl+F9 DaggerMainActivityComponent.create().inject(this); String show = car.show(); Toast.makeText(MainActivity.this, show, Toast.LENGTH_SHORT).show(); }} //Water 抽象类,@Inject无法提供实例 @Inject public Car(Water water) { } //清除子类@Inject //@Inject public HotWater(){ } // @Inject public CoolWater(){ }/** *@Module标记在类上面 *@Provodes标记在方法上 *表示可以通过这个方法获取依赖 */@Modulepublic class WaterModule { @Provides Water provideWater(){ return new HotWater(); }} /** * 在Component中指定Module */@Component(modules = WaterModule.class)public interface MainActivityComponent { void inject(MainActivity mainActivity);} @Module需要和@Provide是需要一起使用的时候才具有作用的,并且@Component也需要指定了Module@Module是告诉Component,可以从这里获取依赖对象。Component就会去找被@Provide标注的方法,相当于构造器的@Inject可以提供依赖,@Component可以指定多个@Module的 Component可以包含多个Module或者Component,这样Component获取依赖时候会自动从多个Module中查找获取,但是,Module间不能有重复方法 123456789101112131415161718在Component中的modules属性中注入多个Module@Component(modules={ModuleA.class,ModuleB.class,...})public interface FruitComponent{ ...}在Component中的dependencies属性中注入多个依赖的Component@Component(dependencies={ComponentA.class,ComponentB.class,...}) public interface FruitComponent{ ...}在Module中注入多个Module@Module(includes={ModuleA.class,ModuleB.class,...})public class FruitModule{ ...} 注入的Module,不管是属于Component本身还是注入的Module的依赖,如果其构造函数为有参构造函数,必须进行初始化 123DaggerFruitComponent.builder() .orangeModule(new OrangeModule(new OrangeBean("贡菊", 6.88, "江西南昌"))) .build(); 依赖规则步骤1:查找Module中是否存在创建该类的方法。 步骤2:若存在创建类方法,查看该方法是否存在参数步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入结束 步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入结束 在Component中,可以声明方法,比如makeApple(),直接提供注入对象实例。DaggerFruitComponent针对每一个@Provides方法创建Provider\<T>实例。在调用makeApple()方法时,实际上是调用的相应Provider的get()方法,获取相应的实例 123public AppleBean makeApple() { return provideAppleProvider.get();} 依赖对象的注入源应该是有两个,一是Module中的@Provides方法,二是使用@Inject注解的构造函数 单例@Singleton,可以做文档注释,提醒是线程安全 123 @Provides @Singleton static Heater provideHeater() { return new ElectricHeater();} @Qualifier是限定符,而@Named则是基于String的限定符,容易写错,推荐@Qualifier 自定义注解 123456789101112 @Provides @CoolQualifier// @Named("Cool") Water provideCoolWater() { return new CoolWater(); }// 另外的一种方式,这里Water是抽象类,可以考虑其他对象Light灯光// @Provides// Water providePot(@CoolQualifier Water w) {// return new Water(w);// } @Component与@SubComponent Car 12345@Injectpublic Car(Water water) { this.water = water; oil = new Oil();} WaterComponent 123456789101112 /** * @Module标记在类上面 * @Provodes标记在方法上 表示可以通过这个方法获取依赖 */@Component(modules = WaterModule.class)public interface WaterComponent { @HotQualifier Water getHotWater(); @CoolQualifier Water getCoolWater();} CarModule 1234567 @Modulepublic class CarModule { @Provides Car provideCar(@CoolQualifier Water water){ return new Car(water); }} CarComponent 12345678 /** * @Module标记在类上面 * @Provodes标记在方法上 表示可以通过这个方法获取依赖 */@Component(modules = CarModule.class, dependencies = WaterComponent.class)public interface CarComponent { Car getCar();} MainActivityComponent 1234 @Component(dependencies = CarComponent.class)public interface MainActivityComponent { void inject(MainActivity mainActivity);} MainActivity 1234567DaggerMainActivityComponent.builder() .carComponent(DaggerCarComponent.builder() .waterComponent(DaggerWaterComponent.create()) .build()) .build() .inject(this);Log.e(TAG, car.show()); CarComponent依赖WaterComponent,将WaterComponent的引用传递给CarComponent,这样CarComponent就可以使用WaterComponent中的方法。在DaggerCarComponent中的getCar()方法,waterComponent.getCoolWater()获取Water 通过@SubComponent实现 1234567891011121314@Component(modules = WaterModule.class)public interface WaterComponent { CarComponent plus(CarModule carModule);}@Subcomponent(modules = CarModule.class)public interface CarComponent{ MainActivityComponent plus();}@Subcomponentpublic interface MainActivityComponent { void inject(MainActivity mainActivity);} Component和SubComponent区别 1234Component dependencies 能单独使用,而Subcomponent必须由Component调用方法获取。Component dependencies 可以很清楚的得知他依赖哪个Component, 而Subcomponent不确定使用上的区别,Subcomponent就像这样DaggerAppComponent.plus(new SharePreferenceModule());使用Dependence可能是这样DaggerAppComponent.sharePreferenceComponent(SharePreferenceComponent.create()) @Scope和@Singleton@Scope管理依赖的生命周期,@Scope对某类注解后,其作用的核心应该是注入器的控制,注入实例时,控制注入器调用缓存的实例还是重新实例,自定义注解,而@Singleton是@Scope默认实现如果有类注入实例的类被@Scope注解,那么其Component必须被相同的Scope注解 1234@Scope@Documented@Retention(RUNTIME)public @interface Singleton {} 依赖实例的注入来源是@Provides方法时,@Provides方法必须被@Scope注解;如果依赖实例的注入来源是@Inject注解的构造函数时,实例类必须被@Scope注解。这样@Scope注解才会有效。也就是说,@Scope实际上是对注入器的控制 Scope控制的实例的注入器是当前Component之内的实例注入器,而不会影响其他的Component中的实例注入器 更好的管理Component之间的组织方式,用自定义的Scope注解标注这些Component,检查有依赖关系或包含关系的Component,若发现Component没有用自定义Scope注解标注,则会报错 编译器会检查 Component管理的Modules,标注Component的自定义Scope注解与Modules中的标注创建类实例方法的注解不一样会报错 单例实例 1234567891011121314151617181920@InjectCar car1;//可以注入多个Car,但是重新创建了一个依赖@InjectCar car2;//使用单例,@Singleton@Modulepublic class CarModule { @Provides @Singleton Car provideCar(@CoolQualifier Water water){ return new Car(water); }}@Singleton@Component(modules = CarModule.class, dependencies = WaterComponent.class)public interface CarComponent { Car getCar();} 编译报错 12Error:(16, 1) 错误: com.willkernel.www.daggerdemo.component.MainActivityComponent (unscoped) cannot depend on scoped components:@Singleton com.willkernel.www.daggerdemo.component.CarComponent 在Component中指定ModuleMainActivityComponent依赖CarComponent而dagger2规定使用单例的Component,子Component也必须标注@Scope但是不能标注@Singleton,不允许,单例依赖单例不符合设计原则需要自定义一个@Scope,例如ActivityScope@Singleton 需要在@Provide和@Component,还有MainActivityComponent中使用@Scope才能够顺利编译,保持局部单例 可以注入多个Car,但是重新创建了一个依赖,使用单例后两个地址一样, 但是在其他Activity中,地址又不一样,属于局部单例,在其他Activity重新 创建了注入器Component,所以Car对象的地址改变了 Dagger正确使用单例 依赖在Component中是单例的(供该依赖的provide方法和对应的Component类使用同一个Scope注解) 对应的Component在App中只初始化一次,每次注入依赖都使用这个Component对象(在Application中创建该Component) App 123456789101112131415public class App extends Application { private CarComponent carComponent; @Override public void onCreate() { super.onCreate(); carComponent = DaggerCarComponent.builder() .waterComponent(DaggerWaterComponent.create()) .build(); } public CarComponent getCarComponent() { return carComponent; }} SecondActivity 12345678@InjectCar car3;DaggerSecondActivityComponent.builder() .carComponent(((App) getApplication()).getCarComponent()) .build() .inject(this); Log.e(TAG, "car3=" + car3.hashCode()); 也可以通过ApplciationScope使用单例 12345678910111213141516171819202122232425@Scope@Retention(RUNTIME)public @interface ApplicationScope {}public class App extends Application{ @Inject Car car; @Override public void onCreate() { super.onCreate(); DaggerAppComponent.builder().carComponent(DaggerCarComponent.builder() .waterComponent(DaggerWaterComponent.create()).build()) .build() .inject(this); } public Car getCar() { return car; } 在SecondeActivity中引用car6=((App)getApplication()).getCar(); Log.e(TAG, "car6=" + car6.hashCode()); MapKey 1234567891011121314151617181920212223242526272829@Component(modules = MapKeyModule.class)public interface MapComponent { Map<String, String> mapkey();}@Modulepublic class MapKeyModule { @Provides @IntoMap @TestKey("foo") String provideFooKey() { return "foo value"; } @Provides @IntoMap @TestKey("bar") String provideBarKey() { return "bar value"; }}@MapKey(unwrapValue = true)public @interface TestKey { String value();}Map<String, String> map = DaggerMapComponent.create().mapkey(); Log.e(TAG, "map " + map.toString()); Lazy通过Lazy提供的实例,在@Inject的时候并不初始化,而是使用的时候,主动调用其get方法来获取实例,并且会缓存该对象 12345@InjectLazy<Car> carLazy;Car carL = carLazy.get();Log.e(TAG, "carL " + carL.show()); Provider有时您需要注入多个实例列表,而不是注入单个值。可以注入一个Provider ,而不只是T。当Provider 每次调用get()方法时,都会执行绑定逻辑并创建一个新的实例,地址不一样,但@Scope注解的类地址是一样的 1234567891011121314@CoffeeScopepublic class CoffeeBean { @Inject public CoffeeBean() { Log.d("test", "Coffee()"); }} @Inject Provider<CoffeeBean> mCoffeeBeanProvider CoffeeBean beanA = mCoffeeBeanProvider.get(); CoffeeBean beanB = mCoffeeBeanProvider.get(); 多个元素绑定并注入到Set 将单个元素注入到Set 1234567@Moduleclass MyModuleA { @Provides @IntoSet static String provideOneString(DepA depA, DepB depB) { return "ABC"; }} Set 注入到Set 1234567 @Moduleclass MyModuleB { @Provides @ElementsIntoSet static Set<String> provideSomeStrings(DepA depA, DepB depB) { return new HashSet<String>(Arrays.asList("DEF", "GHI")); }} 在Component中,表明注入到Set的实例的提供Module,声明setApple()方法,用来提供集合Set 1234567891011121314151617 class Bar { @Inject Bar(Set<String> strings) { assert strings.contains("ABC"); assert strings.contains("DEF"); assert strings.contains("GHI"); }} 或者 @Component(modules = {MyModuleA.class, MyModuleB.class})interface MyComponent { Set<String> strings();}@Test void testMyComponent() { MyComponent myComponent = DaggerMyComponent.create(); assertThat(myComponent.strings()).containsExactly("ABC", "DEF", "GHI");} 也可以通过Provider<Set> or Lazy<Set>来依赖注入实例,但是不能通过Set<Provider>> 增加限定符@Qualifier 1234567891011121314@Moduleclass MyModuleC { @Provides @IntoSet @MyQualifier static Foo provideOneFoo(DepA depA, DepB depB) { return new Foo(depA, depB); }}@Moduleclass MyModuleD { @Provides static FooSetUser provideFooSetUser(@MyQualifier Set<Foo> foos) { ... }} 多个元素绑定并注入到Map在Module中的@Provides方法使用@IntoMap,同时指定该元素的Key(例如@StringKey(“foo”),@ClassKey(Thing.class),@IntKey(12),@LongKey(100L)),没有这个key时,返回null 123456789101112131415161718192021222324252627 @Moduleclass MyModule { @Provides @IntoMap @StringKey("foo") static Long provideFooValue() { return 100L; } @Provides @IntoMap @ClassKey(Thing.class) static String provideThingValue() { return "value for Thing"; }}@Component(modules = MyModule.class)interface MyComponent { Map<String, Long> longsByString(); Map<Class<?>, String> stringsByClass();}@Test void testMyComponent() { MyComponent myComponent = DaggerMyComponent.create(); assertThat(myComponent.longsByString().get("foo")).isEqualTo(100L); assertThat(myComponent.stringsByClass().get(Thing.class)) .isEqualTo("value for Thing");} 如果key是枚举或其他特定的类,使用@MapKey注解,自定义Key 自定义枚举类型,数据包装类key如果不满足指定的返回类型,那么编译时会报错:基本数据类型StringClass(参数化类,? extends Number)枚举类型注解类型以上数据类型的数组 12345678910111213enum MyEnum { ABC, DEF;} @MapKey@interface MyEnumKey { MyEnum value();}@MapKey@interface MyNumberClassKey { Class<? extends Number> value();} 在Module中的@Prvoides使用@IntoMap,并指明key 1234567891011121314 @Moduleclass MyModule { @Provides @IntoMap @MyEnumKey(MyEnum.ABC) static String provideABCValue() { return "value for ABC"; } @Provides @IntoMap @MyNumberClassKey(BigDecimal.class) static String provideBigDecimalValue() { return "value for BigDecimal"; }} 注入器Component 123456789101112 @Component(modules = MyModule.class)interface MyComponent { Map<MyEnum, String> myEnumStringMap(); Map<Class<? extends Number>, String> stringsByNumberClass();}@Test void testMyComponent() { MyComponent myComponent = DaggerMyComponent.create(); assertThat(myComponent.myEnumStringMap().get(MyEnum.ABC)).isEqualTo("value for ABC"); assertThat(myComponent.stringsByNumberClass.get(BigDecimal.class)) .isEqualTo("value for BigDecimal");} 组合的MapKey,设置unwrapValue的值为false,这样key值也可以是数组成员,key不唯一指定,可以有多个不同类型的key 1234567891011121314151617181920 @MapKey(unwrapValue = false)@interface MyKey { String name(); Class<?> implementingClass(); int[] thresholds();}@Moduleclass MyModule { @Provides @IntoMap @MyKey(name = "abc", implementingClass = Abc.class, thresholds = {1, 5, 10}) static String provideAbc1510Value() { return "foo"; }}@Component(modules = MyModule.class)interface MyComponent { Map<MyKey, String> myKeyStringMap();} 使用MapKey,需要生成Mykey的静态方法,就需要使用@AutoAnnotation 123456789101112131415 apt 被annotationProcessor替代 apply plugin: 'com.neenbedankt.android-apt' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' annotationProcessor "com.google.auto.value:auto-value-annotations:1.5" implementation "com.google.auto.value:auto-value:1.5" @AutoAnnotationstatic MyKey createMyKey(String name, Class<?> implementingClass, int[] thresholds) { return new AutoAnnotation_MainActivity_createMyKey(name, implementingClass, thresholds); } MyKeyComponent myKeyComponent = DaggerMyKeyComponent.create(); Log.e(TAG, "myKeyComponent "+myKeyComponent.myKeyStringMap() .get(createMyKey("abc", BigDecimal.class, new int[]{1, 2, 4}))); Mapkey的key在编译期间未知,就不能创建Mapkey的绑定,不过可以先设置绑定Set,再转换为Map绑定 12345678910111213141516171819202122232425262728 @Moduleclass MyModule { @Provides @IntoSet static Map.Entry<Foo, Bar> entryOne(...) { Foo key = ...; Bar value = ...; return new SimpleImmutableEntry(key, value); } @Provides @IntoSet static Map.Entry<Foo, Bar> entryTwo(...) { Foo key = ...; Bar value = ...; return new SimpleImmutableEntry(key, value); }}@Moduleclass MyMapModule { @Provides static Map<Foo, Bar> fooBarMap(Set<Map.Entry<Foo, Bar>> entries) { Map<Foo, Bar> fooBarMap = new LinkedHashMap<>(entries.size()); for (Map.Entry<Foo, Bar> entry : entries) { fooBarMap.put(entry.getKey(), entry.getValue()); } return fooBarMap; }} Dagger并不会自动注入Map\<Foo,Provider\<Bar>>,也就意味着不会提供一个含有Provider值得Map。如果想要获取一个含有Provider的Map,需要在Map.Entry对象中包含Provider值,那么所获取的Map也就含有Provider 123456789101112131415161718@Moduleclass MyModule { @Provides @IntoSet static Map.Entry<Foo, Provider<Bar>> entry( Provider<BarSubclass> barSubclassProvider) { Foo key = ...; return new SimpleImmutableEntry(key, barSubclassProvider); }}@Moduleclass MyProviderMapModule { @Provides static Map<Foo, Provider<Bar>> fooBarProviderMap( Set<Map.Entry<Foo, Provider<Bar>>> entries) { return ...; }} 声明多重绑定@Multibinds-annotated,也可以使用@IntoSet,@IntoMap,@ElementsIntoSet,但是必须有一个 1234567@Moduleabstract class MyModule { @Multibinds abstract Set<Foo> aSet(); @Multibinds @MyQualifier abstract Set<Foo> aQualifiedSet(); @Multibinds abstract Map<String, Foo> aMap(); @Multibinds @MyQualifier abstract Map<String, Foo> aQualifiedMap();} Set或Map多重绑定可以声明任意次数而不会发生错误。Dagger从不实现或调用任何@Multibinds方法添加返回空集合的@ElementsIntoSet方法 1234567@Moduleclass MyEmptySetModule { @Provides @ElementsIntoSet static Set<Foo> primeEmptyFooSet() { return Collections.emptySet(); }} 添加子组件方式 @Component的dependencies属性依赖父组件 1234 @Component(modules = OrangeModule.class, dependencies = FruitComponent.class)public interface OrangeComponent { ***} @Module的Subcomponents添加子组件 12345 @Module(subcomponents = AppleSubcomponent.class)public class FruitModule { ***} @SubComponent声明子组件,需要提供@Subcomponent.Builder接口的Builder,build()来构建子组件 12345678910@Subcomponent(modules = RequestModule.class)interface RequestComponent { RequestHandler requestHandler(); @Subcomponent.Builder interface Builder { Builder requestModule(RequestModule module); RequestComponent build(); }} 添加子组件到父组件,添加子组件类到父组件的@Module的subcomponents属性,然后父组件调用子组件的builder方法 12@Module(subcomponents = RequestComponent.class)class ServerModule {} 父组件的注入器 123456789101112131415161718192021@Singleton@Component(modules = ServerModule.class)interface ServerComponent { RequestRouter requestRouter();}子组件实例注入@Singletonclass RequestRouter { @Inject RequestRouter( Provider<RequestComponent.Builder> requestComponentProvider) {} void dataReceived(Data data) { RequestComponent requestComponent = requestComponentProvider.get() .data(data) .build(); requestComponent.requestHandler() .writeResponse(200, "hello, world"); }} 子组件和作用域,如果没有作用域的绑定,每次注入都会重新创建独立的对象,但是如果有作用域绑定的对象,在作用域生命周期中绑定的对象实例是同一个 子组件和父组件的作用域不能相同,比父组件的生命周期短,两个子组件有不同的作用域实例,即使他们的作用域注解相同 12345678910111213141516@Singleton @Componentinterface RootComponent { SessionComponent.Builder sessionComponent();}@SessionScope @Subcomponentinterface SessionComponent { FooRequestComponent.Builder fooRequestComponent(); BarRequestComponent.Builder barRequestComponent();}@RequestScope @Subcomponentinterface FooRequestComponent {...}@RequestScope @Subcomponentinterface BarRequestComponent {...} 封装子组件在不同Service,不同界面,共享某些绑定,但是这些绑定中也有不需要的绑定实例,这时就需要使用子组件,对绑定实例进行细分 应用ApplicationComponent,注入数据库实例 12345 @Singleton @Component(modules = DatabaseModule.class)interface ApplicationComponent { Database database();} 数据库模块,子组件为DatabaseComponent 12345678910111213 @Module(subcomponents = DatabaseComponent.class)class DatabaseModule { @Provides @Singleton Database provideDatabase( @NumberOfCores int numberOfCores, DatabaseComponent.Builder databaseComponentBuilder) { return databaseComponentBuilder .databaseImplModule(new DatabaseImplModule(numberOfCores / 2)) .build() .database(); }} 数据库实现类模块 123456 @Moduleclass DatabaseImplModule { DatabaseImplModule(int concurrencyLevel) {} @Provides DatabaseConnectionPool provideDatabaseConnectionPool() {} @Provides DatabaseSchema provideDatabaseSchema() {}} 子组件 1234 @Subcomponent(modules = DatabaseImplModule.class)interface DatabaseComponent { @PrivateToDatabase Database database();} 抽象工厂方法定义子组件 扩展多重绑定 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263@Subcomponent(modules = DatabaseImplModule.class)interface DatabaseComponent { @PrivateToDatabase Database database();}@Moduleclass ParentModule { @Provides @IntoMap @StringKey("one") static int one() { return 1; } @Provides @IntoMap @StringKey("two") static int two() { return 2; } @Provides @IntoSet static String a() { return "a" } @Provides @IntoSet static String b() { return "b" }}@Subcomponent(modules = ChildModule.class)interface Child { Map<String, Integer> map(); Set<String> set();}@Moduleclass ChildModule { @Provides @IntoMap @StringKey("three") static int three() { return 3; } @Provides @IntoMap @StringKey("four") static int four() { return 4; } @Provides @IntoSet static String c() { return "c" } @Provides @IntoSet static String d() { return "d" }}Parent parent = DaggerParent.create();Child child = parent.child();assertThat(parent.map().keySet()).containsExactly("one", "two");assertThat(child.map().keySet()).containsExactly("one", "two", "three", "four");assertThat(parent.set()).containsExactly("a", "b");assertThat(child.set()).containsExactly("a", "b", "c", "d"); 模块Module作为工厂方法的参数会在编译时报错,重复模块Module在依赖注入获取对象时引用是运行时错误 123456789101112131415161718192021@Component(modules = {RepeatedModule.class, ...})interface ComponentOne { ComponentTwo componentTwo(RepeatedModule repeatedModule); // COMPILE ERROR! ComponentThree.Builder componentThreeBuilder();}@Subcomponent(modules = {RepeatedModule.class, ...})interface ComponentTwo { ... }@Subcomponent(modules = {RepeatedModule.class, ...})interface ComponentThree { @Subcomponent.Builder interface Builder { Builder repeatedModule(RepeatedModule repeatedModule); ComponentThree build(); }}DaggerComponentOne.create().componentThreeBuilder() .repeatedModule(new RepeatedModule()) // UnsupportedOperationException! .build(); Dagger Android 引入Dagger 1234567// Dagger dependenciesannotationProcessor "com.google.dagger:dagger-compiler:$rootProject.daggerVersion"provided 'org.glassfish:javax.annotation:10.0-b28'compile "com.google.dagger:dagger:$rootProject.daggerVersion"compile "com.google.dagger:dagger-android:$rootProject.daggerVersion"compile "com.google.dagger:dagger-android-support:$rootProject.daggerVersion"annotationProcessor "com.google.dagger:dagger-android-processor:$rootProject.daggerVersion" @Binds注解委托绑定模块的抽象方法,例如绑定Random到SecureRandom模块 1@Binds abstract Random bindRandom(SecureRandom secureRandom); 是@Provides的替代方法,更高效 @Binds method必须是抽象方法必须是一个可以转换为返回值类型的参数,参数是返回值类型的子类或实现类,将返回值类绑定到参数类,将参数暴露为返回值类型,不需要实例化参数对象可以有限定符@Qualified和作用域@Scoped1234@Documented@Retention(RUNTIME)@Target(METHOD)public @interface Binds {} @ContributesAndroidInjector注解的方法生成的返回值对应的AndroidInjector。该注入器是 dagger.Subcomponent的实现,而且是dagger.Module 的子Component用于注解返回具体的Android框架类型(例如:FooActivity、BarFragment、MyService等)的dagger.Module中的无参抽象方法 BindsInstance 标识组件或子组件的Builder方法,绑定对象到组件中的类 12345@Documented@Retention(RUNTIME)@Target(METHOD)@Betapublic @interface BindsInstance {} 注入实例由于DispatchingAndroidInjector在运行时由类查找相应的AndroidInjector.Factory,那么,在基类中,实现HasActivityInjector/HasFragmentInjector接口,在相应的声明周期(onCreate()或者onAttach())内调用AndroidInjection.inject()方法,注入相应的实例。所有每个子类都需要做的是绑定相应的@Subcomponent,从而没有必要在每个实例类中调用AndroidInjection.inject()方法。 12345678910111213141516@Betapublic abstract class DaggerActivity extends Activity implements HasFragmentInjector { @Inject DispatchingAndroidInjector<Fragment> fragmentInjector; @Override protected void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); } @Override public AndroidInjector<Fragment> fragmentInjector() { return fragmentInjector; }} 在dagger.android库中,有一些基本类型,对于Appliaction是DaggerApplication,只需重写applicationInjectoer()方法来返回AndroidInjector\<XxApplication>Dagger提供的基本类型:DaggerActivityDaggerFragmentDaggerServiceDaggerIntentServiceDaggerBroadcastReceiverDaggerContentProvider 参考 github-dagger-demo Dagger 2从浅到深 Dagger2 Dagger2 最清晰的使用教程]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[ConstraintLayout]]></title>
<url>%2F2018%2F03%2F10%2FConstraintLayout%2F</url>
<content type="text"><![CDATA[引入constraint-layout 12345678910allprojects { repositories { jcenter() maven { url 'https://maven.google.com' } }}compile 'com.android.support.constraint:constraint-layout:1.1.0-beta5' 属性 1234567891011121314151617181920212223layout_constraintLeft_toLeftOf左对齐layout_constraintLeft_toRightOf左边和约束控件的右边对齐layout_constraintRight_toLeftOf右边在某组件的左边layout_constraintRight_toRightOf右边在某组件的右边layout_constraintTop_toTopOf上边和某组件的上边对其layout_constraintTop_toBottomOf上边在某组件的下边layout_constraintBottom_toTopOf下边在某组件的上边layout_constraintBottom_toBottomOf下边在某组件的下边layout_constraintBaseline_toBaselineOf组件的基线位置和某组件的基线位置对其(很少用)layout_constraintStart_toEndOflayout_constraintStart_toStartOflayout_constraintEnd_toStartOflayout_constraintEnd_toEndOf属性的值有两种,一种是同层级组件ID,还有就是parent,当值为parent时即是相对于父布局进行定位 偏移设置为0~1之间的值,需要设置上面的水平或垂直相对位置,相应属性: 123456789101112layout_constraintHorizontal_bias // 水平偏移layout_constraintVertical_bias // 垂直偏移设置一个float值,表示宽高比app:layout_constraintDimensionRatio="2"使用比值app:layout_constraintDimensionRatio="16:9"或者app:layout_constraintDimensionRatio="w,16:9"app:layout_constraintDimensionRatio="h,16:9"这种方式,如果没有前缀就代表是宽高比,如果加了前缀H代表比值的第一个数字是高度,W是宽度 add chain autoconnect guideline 距parent默认margin是16dp change margin,adjust bias,达到铺满屏幕效果,不能用match_parent,设置0dp->match_constraint 设置基线约束 packtool 铺满view所在方向的可用空间 Infer Constraints tool 通过计算,给所有元素增加约束,而AutoConnect只针对selected element to the element’s parent. 设置View比例 设置Barrier,修改barrier direction1Barriers start with their barrierDirection set to left. Make sure you update your barrierDirection to right or end. 添加chains,设置margin为0 1234The modes are, in order:Packed: The elements are packed together, as shown above.Spread: The elements are spread out over the available space.Spread inside: Similar to Spread, but the endpoints of the chain are not spread out.]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[CoordinatorLayout]]></title>
<url>%2F2018%2F03%2F09%2FCoordinatorLayout%2F</url>
<content type="text"><![CDATA[CoordinatorLayoutMaterial风格布局,包含在support Library中,结合AppbarLayout,CollapsingToolbarLayout等可达到MD设计风格布局 build.gradle123456789def SUPPORT = "26.1.0"dependencies { implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation "com.android.support:design:$SUPPORT" implementation "com.android.support:cardview-v7:$SUPPORT" implementation "com.android.support:recyclerview-v7:$SUPPORT" implementation 'de.hdodenhof:circleimageview:1.3.0'} styles.xml 123456789<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorAccent</item> <item name="colorPrimaryDark">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:statusBarColor">@color/colorAccent</item> <!--<item name="android:windowDrawsSystemBarBackgrounds">true</item>--> <item name="android:windowTranslucentStatus">true</item> </style> activity_main.xml 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true" android:layout_height="match_parent" tools:context="com.willkernel.app.coordinatorlayoutdemo.MainActivity"> <android.support.design.widget.AppBarLayout android:id="@+id/appBarLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|enterAlways|snap"> <android.support.v7.widget.Toolbar android:id="@+id/main.toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" android:elevation="4dp" android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark" android:title="@string/app_name" app:title="@string/app_name" /> <!--android:background="?attr/colorPrimary"--> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="8dp" app:contentPadding="8dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/main.coordinator.textview" style="@style/TextItem" android:text="@string/item_simple_coordinator_example" /> <TextView android:id="@+id/main.ioexample.textview" style="@style/TextItem" android:text="@string/item_googleio_example" /> <TextView android:id="@+id/main.materialup.textview" style="@style/TextItem" android:text="@string/item_materialup_example" /> <TextView android:id="@+id/main.space.textview" style="@style/TextItem" android:text="@string/item_flexible_space_example" /> <TextView android:id="@+id/main.swipebehavior.textview" style="@style/TextItem" android:text="Swype Behavior example" /> </LinearLayout> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="8dp" app:contentPadding="8dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingExtra="6dp" android:text="@string/about" /> </android.support.v7.widget.CardView> </LinearLayout> </android.support.v4.widget.NestedScrollView></android.support.design.widget.CoordinatorLayout> MaintActivity.java 12Toolbar toolbar=findViewById(R.id.main_toolbar);setSupportActionBar(toolbar);//控制toolbar内部bottom边距 SimpleAppbarLayout 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout 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:background="@android:color/background_light"android:fitsSystemWindows="true"><android.support.design.widget.AppBarLayout android:id="@+id/main.appbar" android:layout_width="match_parent" android:layout_height="300dp" android:fitsSystemWindows="true"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/main.collapsing" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorAccent" app:expandedTitleMarginEnd="46dp" app:expandedTitleMarginStart="46dp" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:scrimAnimationDuration="1000" app:title="@string/app_name" app:titleEnabled="true"> <!--app:scrimAnimationDuration="1000" 颜色过度持续时间--> <!-- app:expandedTitleMarginStart 展开时标题的内边距--> <!-- app:contentScrim="?attr/colorAccent" 折叠时内容背景颜色--> <!-- app:layout_scrollFlags="scroll|exitUntilCollapsed" 和 android:minHeight="200dp" 控制--> <ImageView android:id="@+id/main.backdrop" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:minHeight="200dp" android:scaleType="centerCrop" android:src="@mipmap/material_flat" app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="0.7" /> <!-- app:layout_collapseParallaxMultiplier="0.7" app:layout_collapseMode="parallax" 两者结合使用--> <android.support.v7.widget.Toolbar android:id="@+id/main.toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:fitsSystemWindows="true" android:title="toolbar" app:layout_collapseMode="parallax" app:title="toolbar" /> <!--在titleEnabled=true时,app:title="toolbar" android:title="toolbar"设置标题无效"--> </android.support.design.widget.CollapsingToolbarLayout></android.support.design.widget.AppBarLayout><android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:lineSpacingExtra="8dp" android:padding="@dimen/activity_horizontal_margin" android:text="@string/about" android:textSize="20sp" /></android.support.v4.widget.NestedScrollView><android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/activity_horizontal_margin" android:src="@mipmap/ic_launcher" app:layout_anchor="@id/main.appbar" app:layout_anchorGravity="bottom|right|end" /></android.support.design.widget.CoordinatorLayout> AppBarStateChangeListener 123456789101112131415161718192021222324252627282930public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener { public enum State { EXPANDED, COLLAPSED, IDLE } private State mCurrentState = State.IDLE; @Override public final void onOffsetChanged(AppBarLayout appBarLayout, int i) { if (i == 0) { if (mCurrentState != State.EXPANDED) { onStateChanged(appBarLayout, State.EXPANDED); } mCurrentState = State.EXPANDED; } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) { if (mCurrentState != State.COLLAPSED) { onStateChanged(appBarLayout, State.COLLAPSED); } mCurrentState = State.COLLAPSED; } else { if (mCurrentState != State.IDLE) { onStateChanged(appBarLayout, State.IDLE); } mCurrentState = State.IDLE; }} public abstract void onStateChanged(AppBarLayout appBarLayout, State state);} SimpleCoordinatorActivity 123456789101112131415161718//沉浸式状态栏// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//5.0之上// getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);// getWindow().setStatusBarColor(Color.RED);// } setContentView(R.layout.activity_simple_coordinator); AppBarLayout main_appbar = findViewById(R.id.main_appbar); main_appbar.addOnOffsetChangedListener(stateListener()); Toolbar toolbar=findViewById(R.id.main_toolbar); setSupportActionBar(toolbar); CollapsingToolbarLayout mCollapsingToolbarLayout = findViewById(R.id.main_collapsing); //通过CollapsingToolbarLayout修改字体颜色 mCollapsingToolbarLayout.setExpandedTitleColor(Color.RED);//设置还没收缩时状态下字体颜色 mCollapsingToolbarLayout.setCollapsedTitleTextColor(Color.GREEN);//设置收缩后Toolbar上字体的颜色 Title+Subtitle+NavigationIcon,Toolbar固定并在图片下方 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192<android.support.design.widget.CoordinatorLayout 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.design.widget.AppBarLayout android:id="@+id/io.appbar" android:layout_width="match_parent" android:layout_height="300dp" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="match_parent" app:contentScrim="?attr/colorPrimary" app:expandedTitleMarginEnd="64dp" app:expandedTitleMarginStart="48dp" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--app:contentScrim="?attr/colorPrimary" 没有标题时也要设置背景颜色,不然在滚动的时候有toolbar缺失上半部--> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/material_flat" app:layout_collapseMode="parallax" /> </android.support.design.widget.CollapsingToolbarLayout></android.support.design.widget.AppBarLayout><android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="56dp" android:lineSpacingExtra="8dp" android:text="@string/about" /></android.support.v4.widget.NestedScrollView><android.support.v7.widget.Toolbar android:id="@+id/io.toolbar" android:layout_width="match_parent" android:layout_height="112dp" android:background="?attr/colorAccent" android:elevation="4dp" android:theme="@style/ThemeOverlay.AppCompat.Light" app:layout_anchor="@id/io.appbar" app:layout_anchorGravity="bottom" android:paddingTop="16dp" app:layout_collapseMode="pin" app:navigationIcon="@drawable/abc_ic_ab_back_material" app:subtitle="subtitle" app:theme="@style/ThemeOverlay.AppCompat.Light" app:title="@string/app_name"> <!--android:elevation="4dp" 增加阴影,边界过度更自然--> <!--<LinearLayout--> <!--android:layout_width="match_parent"--> <!--android:layout_height="wrap_content"--> <!--android:layout_gravity="bottom"--> <!--android:layout_marginBottom="8dp"--> <!--android:minHeight="?android:attr/actionBarSize"--> <!--android:orientation="vertical">--> <!--<TextView--> <!--android:layout_width="match_parent"--> <!--android:layout_height="wrap_content"--> <!--android:text="@string/app_name"--> <!--android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse" />--> <!--<TextView--> <!--android:layout_width="match_parent"--> <!--android:layout_height="wrap_content"--> <!--android:layout_marginTop="4dp"--> <!--android:text="subtitle"--> <!--android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" />--> <!--</LinearLayout>--></android.support.v7.widget.Toolbar></android.support.design.widget.CoordinatorLayout>toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } }); Navigation+TabLayout+ViewPager+CircleImage 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout 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.design.widget.AppBarLayout android:id="@+id/profile.appbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="200dp" app:contentScrim="?attr/colorAccent" app:expandedTitleMarginEnd="64dp" app:expandedTitleMarginStart="48dp" app:layout_scrollFlags="scroll|snap"> <!--app:contentScrim="?attr/colorPrimary" 没有标题时也要设置背景颜色,不然在滚动的时候有toolbar缺失上半部--> <ImageView android:id="@+id/profile.image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/material_flat" app:layout_collapseMode="parallax" /> </android.support.design.widget.CollapsingToolbarLayout> <android.support.v7.widget.Toolbar android:id="@+id/profile.toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorAccent" android:minHeight="?attr/actionBarSize" android:paddingTop="16dp" app:layout_scrollFlags="scroll|enterAlways|snap" app:navigationIcon="@drawable/abc_ic_ab_back_material" app:theme="@style/ThemeOverlay.AppCompat.Light"> </android.support.v7.widget.Toolbar> <LinearLayout android:id="@+id/profile.title" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" android:paddingTop="@dimen/activity_horizontal_margin" app:layout_scrollFlags="scroll|enterAlways|snap"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="@string/app_name" android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:gravity="center" android:text="subtitle" android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" /> </LinearLayout> <android.support.design.widget.TabLayout android:id="@+id/profile.tablayout" android:layout_width="match_parent" android:layout_height="?android:attr/actionBarSize" android:paddingTop="24dp" app:tabIndicatorColor="?android:attr/textColorPrimaryInverse" app:tabIndicatorHeight="4dp" app:tabSelectedTextColor="?android:attr/textColorPrimaryInverse" /></android.support.design.widget.AppBarLayout><android.support.v4.view.ViewPager android:id="@+id/profile.viewpager" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /><android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right|end" android:layout_margin="16dp" android:elevation="8dp" /><de.hdodenhof.circleimageview.CircleImageView android:id="@+id/profile.avatar" android:layout_width="96dp" android:layout_height="96dp" android:layout_gravity="center_horizontal" android:elevation="8dp" android:src="@mipmap/user_avatar" app:border_color="#FFF" app:border_width="2dp" app:layout_anchor="@id/profile.title" app:layout_anchorGravity="center_horizontal|top" app:layout_scrollFlags="scroll" /></android.support.design.widget.CoordinatorLayout> ProfileActivity.java 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 profile_avatar = findViewById(R.id.profile_avatar); TabLayout tabLayout = findViewById(R.id.profile_tablayout); ViewPager viewPager = findViewById(R.id.profile_viewpager); AppBarLayout appBarLayout = findViewById(R.id.profile_appbar); appBarLayout.addOnOffsetChangedListener(this); mMaxScrollSize = appBarLayout.getTotalScrollRange(); viewPager.setAdapter(new TabsAdapter(getSupportFragmentManager())); tabLayout.setupWithViewPager(viewPager); Toolbar toolbar = findViewById(R.id.profile_toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } });}public static void start(Context c) { c.startActivity(new Intent(c, MaterialUpConceptActivity.class));}@Overridepublic void onOffsetChanged(AppBarLayout appBarLayout, int i) { if (mMaxScrollSize == 0) { mMaxScrollSize = appBarLayout.getTotalScrollRange(); } int percent = Math.abs(i) * 100 / mMaxScrollSize; if (percent >= PERCENTAGE_TO_ANIMATE_AVATAR && mIsAvatarShown) { mIsAvatarShown = false; profile_avatar.animate().scaleX(0).scaleY(0).setDuration(200).start(); } if (percent <= PERCENTAGE_TO_ANIMATE_AVATAR && !mIsAvatarShown) { mIsAvatarShown = true; profile_avatar.animate().scaleX(1).scaleY(1).setDuration(200).start(); }}private class TabsAdapter extends FragmentPagerAdapter { private static final int TAB_COUNT = 2; public TabsAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return MyFragment.newInstance(); } @Override public int getCount() { return TAB_COUNT; } @Override public CharSequence getPageTitle(int position) { return "Tab" + position; }} MyFragment 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253private RecyclerView rootView; public static MyFragment newInstance() { return new MyFragment(); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { rootView = (RecyclerView) inflater.inflate(R.layout.fragment_page, container, false); return rootView; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initRecyclerView(); } private void initRecyclerView() { rootView.setAdapter(new MyPageAdapter(10)); } private class MyPageAdapter extends RecyclerView.Adapter<MyVH> { private final int count; public MyPageAdapter(int count) { this.count = count; } @Override public MyVH onCreateViewHolder(ViewGroup parent, int viewType) { View item = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_card, parent, false); return new MyVH(item); } @Override public void onBindViewHolder(MyVH holder, int position) { } @Override public int getItemCount() { return count; } } private class MyVH extends RecyclerView.ViewHolder { public MyVH(View itemView) { super(itemView); } } fragment_page.xml 123456789<android.support.v7.widget.RecyclerView 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:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollbars="vertical"app:layoutManager="android.support.v7.widget.LinearLayoutManager"tools:listitem="@layout/list_item_card" /> 图片被内容覆盖一部分,形成上下两层,FAB跟随AppbarLayout放大缩小 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout 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.design.widget.AppBarLayout android:id="@+id/flex.appbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:contentScrim="?attr/colorAccent" app:expandedTitleMarginBottom="90dp" app:expandedTitleMarginStart="32dp" app:layout_scrollFlags="scroll|snap|exitUntilCollapsed" app:title="flex"> <!--app:expandedTitleMarginBottom="90dp" 被遮挡后需要设置底部内边距--> <ImageView android:id="@+id/flex.image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/material_flat" app:layout_collapseMode="parallax" /> <android.support.v7.widget.Toolbar android:id="@+id/flex.toolbar" android:layout_width="match_parent" android:layout_height="?android:attr/actionBarSize" android:layout_marginTop="@dimen/activity_horizontal_margin" android:background="@null" app:layout_collapseMode="pin" app:layout_scrollFlags="scroll|enterAlways|snap" app:navigationIcon="@drawable/abc_ic_ab_back_material" app:theme="@style/ThemeOverlay.AppCompat.Light" /> </android.support.design.widget.CollapsingToolbarLayout></android.support.design.widget.AppBarLayout><android.support.v4.widget.NestedScrollView android:id="@+id/flex.scroll" android:layout_width="match_parent" android:layout_height="match_parent" app:behavior_overlapTop="78dp" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <!--app:behavior_overlapTop="78dp" 图片被可滚动的ScrollView覆盖内容--> <android.support.v7.widget.CardView android:id="@+id/flex.cardview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" app:cardBackgroundColor="@android:color/white" app:cardCornerRadius="4dp" app:cardElevation="4dp" app:contentPaddingBottom="16dp" app:contentPaddingLeft="16dp" app:contentPaddingRight="16dp"> <TextView android:id="@+id/flex.text" android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/about" /> </android.support.v7.widget.CardView></android.support.v4.widget.NestedScrollView><android.support.design.widget.FloatingActionButton android:id="@+id/flex.fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:elevation="8dp" app:layout_anchor="@id/flex.cardview" app:layout_anchorGravity="end|right" /></android.support.design.widget.CoordinatorLayout> FlexActivity.java 1234567891011121314151617181920212223242526272829private static final int PERCENTAGE_TO_SHOW_IMAGE = 20;appBarLayout.addOnOffsetChangedListener(this); mFab = findViewById(R.id.flex_fab); Toolbar toolbar = findViewById(R.id.flex_toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } });}@Overridepublic void onOffsetChanged(AppBarLayout appBarLayout, int i) { if (mMaxScrollSize == 0) { mMaxScrollSize = appBarLayout.getTotalScrollRange(); } if (mMaxScrollSize == 0) return; int currentScrollPercentage = (Math.abs(i)) * 100 / mMaxScrollSize; if (currentScrollPercentage >= PERCENTAGE_TO_SHOW_IMAGE && !mIsImageHidden) { mIsImageHidden = true; mFab.animate().scaleX(0).scaleY(0).start(); } if (currentScrollPercentage < PERCENTAGE_TO_SHOW_IMAGE && mIsImageHidden) { mIsImageHidden = false; mFab.animate().scaleX(1).scaleY(1).start(); }} 设置CardView在CoordinatorLayout中的滑动移除Behavior123456789101112131415161718SwipeDismissBehavior swipeDismissBehavior = new SwipeDismissBehavior(); swipeDismissBehavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY); swipeDismissBehavior.setListener(new SwipeDismissBehavior.OnDismissListener() { @Override public void onDismiss(View view) { Toast.makeText(SwipeBehaviorExampleActivity.this, "Card swiped !!", Toast.LENGTH_SHORT).show(); } @Override public void onDragStateChanged(int state) { } });CardView cardView = findViewById(R.id.swipe_card);LayoutParams layoutParams = (LayoutParams) cardView.getLayoutParams();layoutParams.setBehavior(swipeDismissBehavior);]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Git]]></title>
<url>%2F2018%2F03%2F09%2FGit%2F</url>
<content type="text"><![CDATA[入门 .git 的隐藏目录是你的本地仓库(Local Repository) git log 查看历史 git log -p查看详细历史 git log --stat 查看简要统计 git show e66666/branch查看指定commit,加文件名看指定文件 git diff --staged/--cached查看当前工作目录与暂存区的不同,可以看到即将添加到暂存区的改动内容,git diff HEAD看到当前工作目录与上一个commit的不同 commit 的 SHA-1 校验和commit 8cf88cf35ce40cb91488e7d9b12cf46463fedc2f git status untracked files (未追踪的文件) 使用 git add git.txt 来开始追踪文件 从 “Untracked files” 变成了 “Changes to be commited”,该文件变成已暂存staged,被记录到 staging area(暂存区) git commit -m "add git.txt"提交代码到本地仓库 缓存GitHub密码,在设置了SSH key后,git config --global credential.helper wincred git push到远端仓库 git pull将远端仓库代码同步到本地仓库 push冲突,由于 GitHub 的远端仓库上含有本地仓库没有的内容,所以这次 push 被拒绝,解决办法:先用 pull 把远端仓库上的新内容取回到本地和本地合并,合并后的代码会自动提交,然后再把合并后的本地仓库向远端仓库推送 HEAD、master、branch commit 后面括号里的 HEAD -> master, origin/master, origin/HEAD ,是指向这个 commit 的引用,也可以使用SHA-1指代commit HEAD 当前 commit 的引用 当前工作目录对应的commit,checkout/reset会改变当前commit branch HEAD 除了可以指向 commit,还可以指向一个 branch,HEAD指向分支master,间接指向对应的commit,当git commit有新的提交时,HEAD会和master一起指向新的commit master 默认的主分支,和其他分支平等,分支可以理解为初始commit到当前分支指向的commit的路径 branch 的创建、切换和删除 创建 branch git branch feature1 切换分支git checkout feature1 HEAD 就会指向新建的 branch,执行git checkout -b feature1是上述两个合并执行 删除分支git branch -d feature1,HEAD 指向的 branch 不能删除。如果要删除 HEAD 指向的 branch,需要先用 checkout 把 HEAD 指向其他分支,没有被合并到 master 过的 branch 在删除时会失败,确认删除使用git branch -D feature1 push git push当前master分支,把该分支上的未上传的commit,以及当前master所指向的commit引用一并上传到远端 切换到feature1,执行push,origin feature1参数代表目标仓库和目标分支 12git checkout feature1git push origin feature1 git config push.default current 推送到与当前分支相同的远端分支,参考git config HEAD指向默认分支master,在git push时,只会上传当前分支的指向,例如push后,本地feature1指向第5个commit,push后远端feature1分支指向第5个commit,而HEAD指向不会切换到feature1的指向,默认指向master分支的commitmergepull执行了fetch拉取到本地, merge合并远端commit和本地commit git merge branch1 把branch1分支合并到master分支,生成一个新的commit 两个分支修改相同内容,造成合并冲突,可以删除冲突的内容重新合并 12git add git.txtgit commit 也可以取消合并git merge --abort,回到之前的commit状态 当HEAD领先于branch的commit,在同一路径上,merge 是空操作,不需要创建新的commit 当HEAD落后于目标branch的commit,在同一路径上,merge会直接将HEAD指向目标commit,这个操作即是 fast-forward 快速前移,在本地仓库中commit落后于远端代码,造成本地HEAD落后与目标commit,git pull就会造成fast-forward origin/master和 origin/HEAD 是对远端仓库的 master 和 HEAD 的本地镜像,在 git pull 中的第一步git fetch 下载远端仓库内容时,这两个镜像引用得到了更新,origin/master 和 origin/HEAD 移动到了最新的 commitFeature Branching 任何新的功能(feature)或 bug 修复全都新建一个 branch branch 写完后,合并到 master,然后删掉这个 branch 开发一个新的功能,创建新的分支 1git checkout -b feature1 功能基本完成后,push到远端分支feature1 1git push origin feature1 review 复审代码,通过后合并分支到master 123456git pullgit chekcout feature1···git checkout mastergit pull # merge 之前 pull 一下,让 master 更新到和远程仓库同步git merge feature1 合并后的结果push到远端仓库,并删除本地和远端分支feature1 123git pushgit branch -d feature1git push origin -d feature1 也可以使用pull request,查看commit,然后合并分支 rebase 变基 重新设置基础点,切换到branch1变基,防止远端仓库的commit被剔除 12git checkout branch1git rebase master 回到master,将master移动到最新commit 提交代码错误 对最新一条 commit 进行修正git commit --amend,替换原来的commit,生成新的一个commit交互式 rebase rebase -i:交互式 rebase,是rebase --interactive缩写,指定某个commit进行修改 1git rebase -i HEAD^^ 偏移符合^ 一个或多个这个符号往回偏移对应个数的commit 偏移符号~n 向前偏移n个commit 编辑界面,选择commit和对应的操作,将需要编辑的commit的pick改为edit,然后修改需要修改的内容 12edit beeee 修改...pick edddd 修改... 用 commit --amend 来把修正应用到当前的 commit 12git add git.txtgit commit --amend 继续rebase,把后面的commit直接应用上去 1git rebase --continue 丢弃重新提交 git reset --hard HEAD^撤销提交的commit,重新回到上一个commit 之前的提交需要丢弃 12git rebase -i HEAD^^ 回到上个commitdrop 删掉这个commit git rebase 第3个commit 会自动选择分岔点,并将4,5commit提交到3所在的路径 rebase --onto额外指定起点,只想把 5 提交到 3 上,不想附带上 41git rebase --onto 第3个commit 第4个commit branch1 rebase --onto来撤销提交,起点不包含在rebase序列,下面的例子是丢弃HEAD^,branch1的commit1git rebase --onto HEAD^^ HEAD^ branch1 push到远端仓库的commit错误 出错的commit已经push到远端分支,需要修改或删除掉,重新修改后,push commit时会报错,因为远端包含本地没有的commit,所以需要忽略冲突,强制push 1git push origin branch1 -f 出错的commite已经合并到master,不能修改后强制push,应该直接revert撤销commit,然后再push revert后的代码 1git revert HEAD^ reset 撤销最新提交的commit,其实是重置HEAD,branch的commit位置到父commit 1git reset --hard HEAD^ 可以把HEAD,branch移动其他commit 1git reset --hard branch2 reset --hard:重置工作目录,工作目录的修改也被撤销,无论是否已add到暂存区 reset --soft:保留工作目录改动,并将文件的改动放到暂存区 reset 不加参数:工作目录的修改、暂存区的内容以及由 reset 所导致的新的文件差异,都会被放进工作目录1234567891011121314修改了 git.txt 和 readme.md,并把 git.txt 放进了暂存区当前push的commit中新增了文件app.txt此时执行git reset HEAD^工作目录的内容都会保存,暂存区被清空git statuschanges not staged:modified git.txtmodified readme.mduntracked files:app.txt checkout 切换分支git checkout branch1,也可以切换到指定的commit 123456git checkout HEAD^^git checkout master~3git checkout 32d434git checkout 32d434^checkout -- 文件名 的格式来撤销指定文件的修改 reset HEAD和branch一起移动,而checkout不会,HEAD会指向新的commit 12HEAD 和 branch 脱离而不移动 HEAD,HEAD直接指向commitgit checkout --detach stash 将当前改动内容保存到本地其他地方,不会被删除或提交,这样就可以把工作目录的改动清空,所有的改动被保存,之后需要还原的statsh之前的内容,执行1234git stash popGit会忽略未被追踪的文件,如果也要stash保存untracked文件,即没有add到缓存区的文件git stash -u reflog(reference log) 误删了branch1,可以查看HEAD的移动历史,找到删除之前的commit,切换回删除之前的commit,重新创建同名分支,注意reflog要及时,因为不再引用或间接指向的commit会被Git回收查看其他引用的reflog,git reflog master123git refloggit checkout ccdde32git checkout -b branch1 gitignore忽略不需要被管理的文件 其他 tag是一个和 branch 非常相似的概念,它和 branch 最大的区别是:tag 不能移动,tag 被用来在关键版本处打标记用 cherry-pick 是一种特殊的合并操作,使用它可以点选一批 commits,按序合并 git-docs Pro git]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[NDK]]></title>
<url>%2F2018%2F03%2F09%2FNDK%2F</url>
<content type="text"></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>NDK</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MVP(loader/dagger/rxjava2)]]></title>
<url>%2F2018%2F03%2F09%2FMVP%2F</url>
<content type="text"><![CDATA[MVP概念 View视图层,包含各种界面相关功能,例如Activity,Fragment,View,Adapter,专注于交互,一般持有Presenter的引用,或者通过依赖注入(Dagger)方式获得Presenter实例,将非UI逻辑操作委托给Presenter Presenter逻辑控制层,充当中间人,隔离View层,Model层,接收View层的数据请求,分发给Model层处理,监听Model处理结果,将结果反馈给View,实项界面的刷新 Model封装网络数据请求,本地数据请求,对Presenter提供简单易用的接口 MVP,MVC区别 MVP 1、模型与视图完全分离,我们可以修改视图而不影响模型2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试) MVP优缺点 可以Mock一个实现了接口Contract.View的View对象便于测试,业务逻辑代码与View的隔离解耦 缺点:View中持有Presenter,Presenter中持有View,增加了复杂度,复杂业务中Presenter的代码会臃肿 MVC中Model和View直接通信的,View中会包含业务逻辑代码,Controller控制层通常在Activity中实现业务逻辑代理,操作Model,但是不操作View,对View无知,Model数据更新完对视图进行更新,用户得到反馈 MVC优缺点,逻辑代码在Controller中,模块化,业务逻辑代码更改后,不需要更新View,Model;测试困难,Activity包含业务代码,成为了Controller层,Model,View之间存在耦合 (以下项目来自android-architecture-todoapp)mvp BaseView 123public interface BaseView<T> { void setPresenter(T presenter);} BasePresenter 123public interface BasePresenter { void start();} TaskContract接口包装Presenter,View 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657interface View extends BaseView<Presenter> { void setLoadingIndicator(boolean active); void showTasks(List<Task> tasks); void showAddTask(); void showTaskDetailsUi(String taskId); void showTaskMarkedComplete(); void showTaskMarkedActive(); void showCompletedTasksCleared(); void showLoadingTasksError(); void showNoTasks(); void showActiveFilterLabel(); void showCompletedFilterLabel(); void showAllFilterLabel(); void showNoActiveTasks(); void showNoCompletedTasks(); void showSuccessfullySavedMessage(); boolean isActive(); void showFilteringPopUpMenu(); } interface Presenter extends BasePresenter { void result(int requestCode, int resultCode); void loadTasks(boolean forceUpdate); void addNewTask(); void openTaskDetails(@NonNull Task requestedTask); void completeTask(@NonNull Task completedTask); void activateTask(@NonNull Task activeTask); void clearCompletedTasks(); void setFiltering(TasksFilterType requestType); TasksFilterType getFiltering(); } TaskPresenter 实现TaskContract.Presenter,持有TaskContract.View对象,在Activity中初始化并传入View,Model 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758public class TasksPresenter implements TasksContract.Presenter { private final TasksRepository mTasksRepository; private final TasksContract.View mTasksView; public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) { mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null"); mTasksView = checkNotNull(tasksView, "tasksView cannot be null!"); //View必须实现TasksContract.View mTasksView.setPresenter(this);} mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() { @Override public void onTasksLoaded(List<Task> tasks) { List<Task> tasksToShow = new ArrayList<Task>(); // This callback may be called twice, once for the cache and once for loading // We filter the tasks based on the requestType for (Task task : tasks) { switch (mCurrentFiltering) { case ALL_TASKS: tasksToShow.add(task); break; case ACTIVE_TASKS: if (task.isActive()) { tasksToShow.add(task); } break; case COMPLETED_TASKS: if (task.isCompleted()) { tasksToShow.add(task); } break; default: tasksToShow.add(task); break; } } // The view may not be able to handle UI updates anymore if (!mTasksView.isActive()) { return; } if (showLoadingUI) { mTasksView.setLoadingIndicator(false); } processTasks(tasksToShow); } @Override public void onDataNotAvailable() { // The view may not be able to handle UI updates anymore if (!mTasksView.isActive()) { return; } mTasksView.showLoadingTasksError(); } }); TasksFragment 持有TasksContract.Presenter对象,并实现TasksContract.View,Fragment实例化后在Presenter中taskView.setPresenter(this),在taskView中通过Presenter获取数据,并在Presenter中刷新内容,在taskView中响应 1234567public class TasksFragment extends Fragment implements TasksContract.Viewprivate TasksContract.Presenter mPresenter;@Overridepublic void setPresenter(@NonNull TasksContract.Presenter presenter) { mPresenter = checkNotNull(presenter);} TaskActivity 1234567891011TasksFragment tasksFragment =(TasksFragment)getSupportFragmentManager().findFragmentById(R.id.contentFrame);if (tasksFragment == null) { // Create the fragment tasksFragment = TasksFragment.newInstance(); ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), tasksFragment, R.id.contentFrame);}// Create the presentermTasksPresenter = new TasksPresenter(Injection.provideTasksRepository(getApplicationContext()), tasksFragment);//set filtermTasksPresenter.setFiltering(currentFiltering); ToDoDatabase RoomDatabase包含一个Task表 123456789101112131415161718192021@Database(entities = {Task.class}, version = 1)public abstract class ToDoDatabase extends RoomDatabase { private static ToDoDatabase INSTANCE; public abstract TasksDao taskDao(); private static final Object sLock = new Object(); public static ToDoDatabase getInstance(Context context) { synchronized (sLock) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), ToDoDatabase.class, "Tasks.db") .build(); } return INSTANCE; } }} TasksDao 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667@Daopublic interface TasksDao { /** * Select all tasks from the tasks table. * * @return all tasks. */ @Query("SELECT * FROM Tasks") List<Task> getTasks(); /** * Select a task by id. * * @param taskId the task id. * @return the task with taskId. */ @Query("SELECT * FROM Tasks WHERE entryid = :taskId") Task getTaskById(String taskId); /** * Insert a task in the database. If the task already exists, replace it. * * @param task the task to be inserted. */ @Insert(onConflict = OnConflictStrategy.REPLACE) void insertTask(Task task); /** * Update a task. * * @param task task to be updated * @return the number of tasks updated. This should always be 1. */ @Update int updateTask(Task task); /** * Update the complete status of a task * * @param taskId id of the task * @param completed status to be updated */ @Query("UPDATE tasks SET completed = :completed WHERE entryid = :taskId") void updateCompleted(String taskId, boolean completed); /** * Delete a task by id. * * @return the number of tasks deleted. This should always be 1. */ @Query("DELETE FROM Tasks WHERE entryid = :taskId") int deleteTaskById(String taskId); /** * Delete all tasks. */ @Query("DELETE FROM Tasks") void deleteTasks(); /** * Delete all completed tasks from the table. * * @return the number of tasks deleted. */ @Query("DELETE FROM Tasks WHERE completed = 1") int deleteCompletedTasks();} TasksLocalDataSource 1234567891011@Override public void saveTask(@NonNull final Task task) { checkNotNull(task); Runnable saveRunnable = new Runnable() { @Override public void run() { mTasksDao.insertTask(task); } }; mAppExecutors.diskIO().execute(saveRunnable); } 保存数据 1mTasksRepository.saveTask(new Task(title, description, mTaskId)); DiskIOThreadExecutor 12345678910111213public class DiskIOThreadExecutor implements Executor { private final Executor mDiskIO; public DiskIOThreadExecutor() { mDiskIO = Executors.newSingleThreadExecutor(); } @Override public void execute(@NonNull Runnable command) { mDiskIO.execute(command); }} mvp-loader Presenter中持有View,Loader,LoaderManager(提供异步加载),Repository,当数据发生变化时,Loader自动刷新View 123456789101112public class TasksPresenter implements TasksContract.Presenter, LoaderManager.LoaderCallbacks<List<Task>> { private final static int TASKS_QUERY = 1; private final TasksRepository mTasksRepository; private final TasksContract.View mTasksView; private final TasksLoader mLoader; private final LoaderManager mLoaderManager; TaskRepository,在MVP中需要回调刷新View,而在Loader机制中自动刷新 12345678910111213141516171819202122232425262728293031323334353637383940414243private final TasksDataSource mTasksRemoteDataSource;private final TasksDataSource mTasksLocalDataSource;private List<TasksRepositoryObserver> mObservers = new ArrayList<TasksRepositoryObserver>();// Update the UInotifyContentObserver();···//从缓存Map中获取数据Map<String, Task> mCachedTasks;public List<Task> getCachedTasks() { return mCachedTasks == null ? null : new ArrayList<>(mCachedTasks.values());}public Task getCachedTask(String taskId) { return mCachedTasks.get(taskId);}//获取任务@Nullable@Overridepublic List<Task> getTasks() { List<Task> tasks = null; if (!mCacheIsDirty) { // Respond immediately with cache if available and not dirty if (mCachedTasks != null) { tasks = getCachedTasks(); return tasks; } else { // Query the local storage if available. tasks = mTasksLocalDataSource.getTasks(); } } // To simplify, we'll consider the local data source fresh when it has data. if (tasks == null || tasks.isEmpty()) { // Grab remote data if cache is dirty or local data not available. tasks = mTasksRemoteDataSource.getTasks(); // We copy the data to the device so we don't need to query the network next time saveTasksInLocalDataSource(tasks); } processLoadedTasks(tasks); return getCachedTasks();} Fragment中涉及Loader加载数据,自动刷新流程 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 //在Activity中对Loader,LoaderManager,Presenter实例化 // Create the presenter TasksRepository repository = Injection.provideTasksRepository(getApplicationContext()); TasksLoader tasksLoader = new TasksLoader(getApplicationContext(), repository); mTasksPresenter = new TasksPresenter( tasksLoader, getSupportLoaderManager(), repository, tasksFragment ); //FragmentManager的performStart()中也有回调LoadFinished()针对有缓存数据时刷新View void performStart() { ··· onStart(); ··· if (mLoaderManager != null) { mLoaderManager.doReportStart(); } } //Fragment的onResume()对LoadManager进行初始化 @Override public void onResume() { super.onResume(); mPresenter.start(); } //Presenter初始化 @Override public void start() { mLoaderManager.initLoader(TASK_QUERY, null, this); } //LoadManager开始加载 void start(){ mLoader.startLoading(); } //LoaderManager中加载完成后回调callOnLoadFinished void finishRetain() { ··· callOnLoadFinished(mLoader, mData); } void reportStart() { if (mStarted) { if (mReportNextStart) { mReportNextStart = false; if (mHaveData && !mRetaining) { callOnLoadFinished(mLoader, mData); } } } } //Presenter加载数据 public void loadTasks(boolean forceUpdate) { if (forceUpdate || mFirstLoad) { mFirstLoad = false; mTasksRepository.refreshTasks(); } else { showFilteredTasks(); } } //加载完成后 @Override public void onLoadFinished(Loader<List<Task>> loader, List<Task> data) { mTasksView.setLoadingIndicator(false); mCurrentTasks = data; if (mCurrentTasks == null) { mTasksView.showLoadingTasksError(); } else { showFilteredTasks(); } } //Presenter处理数据返回结果 private void processTasks(List<Task> tasks) { if (tasks.isEmpty()) { // Show a message indicating there are no tasks for that filter type. processEmptyTasks(); } else { // Show the list of tasks mTasksView.showTasks(tasks); // Set the filter label's text. showFilterLabel(); } } //Fragment显示数据@Override public void showTasks(List<Task> tasks) { mListAdapter.replaceData(tasks); mTasksView.setVisibility(View.VISIBLE); mNoTasksView.setVisibility(View.GONE); } mvp-dagger DaggerApplication 注入Activity,Fragment,Service,BroadService,ContentProvider等成员,并在相应类中的onCreate()/onAttach()/onCreate()/onReceive()/onCreate()注入 1AndroidInjection.inject(this); DaggerApplication 123456789101112public abstract class DaggerApplication extends Application implements HasActivityInjector, HasFragmentInjector, HasServiceInjector, HasBroadcastReceiverInjector, HasContentProviderInjector { @Inject DispatchingAndroidInjector<Activity> activityInjector; @Inject DispatchingAndroidInjector<BroadcastReceiver> broadcastReceiverInjector; @Inject DispatchingAndroidInjector<Fragment> fragmentInjector; @Inject DispatchingAndroidInjector<Service> serviceInjector; @Inject DispatchingAndroidInjector<ContentProvider> contentProviderInjector; ToDoApplication 1234567891011121314public class ToDoApplication extends DaggerApplication { @Inject TasksRepository tasksRepository; @Override protected AndroidInjector<? extends DaggerApplication> applicationInjector() { return DaggerAppComponent.builder().application(this).build(); } @VisibleForTesting public TasksRepository getTasksRepository() { return tasksRepository; }} ApplicationModule 通过AppComponent依赖注入,提供给子组件Context实例 123456@Modulepublic abstract class ApplicationModule { //expose Application as an injectable context @Binds abstract Context bindContext(Application application);} AppComponent 在Dagger.Android中自动生成并定位子组件 123456789101112131415161718192021@Singleton@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class, ActivityBindingModule.class, AndroidSupportInjectionModule.class})public interface AppComponent extends AndroidInjector<ToDoApplication> { TasksRepository getTasksRepository(); // Gives us syntactic sugar. we can then do DaggerAppComponent.builder().application(this).build().inject(this); // never having to instantiate any modules or say which module we are passing the application to. // Application will just be provided into our app graph now. @Component.Builder interface Builder { @BindsInstance AppComponent.Builder application(Application application); AppComponent build(); }} TasksRepositoryModule 123456789101112131415161718192021222324252627282930313233343536@Moduleabstract public class TasksRepositoryModule { private static final int THREAD_COUNT = 3; @Singleton @Binds @Local abstract TasksDataSource provideTasksLocalDataSource(TasksLocalDataSource dataSource); @Singleton @Binds @Remote abstract TasksDataSource provideTasksRemoteDataSource(FakeTasksRemoteDataSource dataSource); @Singleton @Provides static ToDoDatabase provideDb(Application context) { return Room.databaseBuilder(context.getApplicationContext(), ToDoDatabase.class, "Tasks.db") .build(); } @Singleton @Provides static TasksDao provideTasksDao(ToDoDatabase db) { return db.taskDao(); } @Singleton @Provides static AppExecutors provideAppExecutors() { return new AppExecutors(new DiskIOThreadExecutor(), Executors.newFixedThreadPool(THREAD_COUNT), new AppExecutors.MainThreadExecutor()); }} AppExecutors 1234567891011121314151617181920212223242526272829303132333435363738@Singletonpublic class AppExecutors { private static final int THREAD_COUNT = 3; private final Executor diskIO; private final Executor networkIO; private final Executor mainThread; public AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) { this.diskIO = diskIO; this.networkIO = networkIO; this.mainThread = mainThread; } public Executor diskIO() { return diskIO; } public Executor networkIO() { return networkIO; } public Executor mainThread() { return mainThread; } public static class MainThreadExecutor implements Executor { private Handler mainThreadHandler = new Handler(Looper.getMainLooper()); @Override public void execute(@NonNull Runnable command) { mainThreadHandler.post(command); } }} TasksModule中的Presenter和View注入到TasksActivity,提供实例 123456789@Modulepublic abstract class TasksModule { @FragmentScoped @ContributesAndroidInjector abstract TasksFragment tasksFragment(); @ActivityScoped @Binds abstract TasksContract.Presenter taskPresenter(TasksPresenter presenter);} ActivityBindingModule,Android 注解处理器自动通过指定的模块生成子组件,并绑定AppComponent父组件,不需要告诉父组件子组件有哪些,也不需要告诉子组件谁是父组件 123456789101112131415161718@Modulepublic abstract class ActivityBindingModule { @ActivityScoped @ContributesAndroidInjector(modules = TasksModule.class) abstract TasksActivity tasksActivity(); @ActivityScoped @ContributesAndroidInjector(modules = AddEditTaskModule.class) abstract AddEditTaskActivity addEditTaskActivity(); @ActivityScoped @ContributesAndroidInjector(modules = StatisticsModule.class) abstract StatisticsActivity statisticsActivity(); @ActivityScoped @ContributesAndroidInjector(modules = TaskDetailPresenterModule.class) abstract TaskDetailActivity taskDetailActivity();} TasksActivity 123456public class TasksActivity extends DaggerAppCompatActivity { @Inject TasksPresenter mTasksPresenter; @Inject Lazy<TasksFragment> taskFragmentProvider;} TasksFragment 123456789@ActivityScopedpublic class TasksFragment extends DaggerFragment implements TasksContract.View { @Inject TasksContract.Presenter mPresenter; @Injectpublic TasksFragment() { // Requires empty public constructor } TasksPresenter 12345678910@ActivityScopedfinal class TasksPresenter implements TasksContract.Presenter { private final TasksRepository mTasksRepository; @Nullable private TasksContract.View mTasksView; @Inject TasksPresenter(TasksRepository tasksRepository) { mTasksRepository = tasksRepository; } TasksRepository 123456789@Singletonpublic class TasksRepository implements TasksDataSource { @Inject TasksRepository(@Remote TasksDataSource tasksRemoteDataSource, @Local TasksDataSource tasksLocalDataSource) { mTasksRemoteDataSource = tasksRemoteDataSource; mTasksLocalDataSource = tasksLocalDataSource; } 在TasksActivity中通过依赖注入@Inject,TasksPresenter,TasksRepository,TasksFragment已经实例化 ActivityScope,FragmentScope 12345678910@Documented@Scope@Retention(RetentionPolicy.RUNTIME)public @interface ActivityScoped {}@Scope@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface FragmentScoped {} ToDoDatabase 1234@Database(entities = {Task.class}, version = 1)public abstract class ToDoDatabase extends RoomDatabase { public abstract TasksDao taskDao();} mvp-rxjava2 BaseView 123public interface BaseView<T> { void setPresenter(T presenter);} BasePresenter 1234public interface BasePresenter { void subscribe(); void unsubscribe();} 同样,在TasksActivity中持有TasksPresenter 12345// Create the presenter mTasksPresenter = new TasksPresenter( Injection.provideTasksRepository(getApplicationContext()), tasksFragment, Injection.provideSchedulerProvider()); TasksPresenter 持有TasksFragment对象mTasksView,网络请求等任务的集合CompositeDisposable,用于取消任务,避免内存泄漏 TasksPresenter 123456789@Override public void subscribe() { loadTasks(false); }@Override public void unsubscribe() { mCompositeDisposable.clear(); } 对应的在TasksFragment 1234567891011121314@Overridepublic void onResume() { super.onResume(); mPresenter.subscribe();}@Overridepublic void onPause() { super.onPause(); mPresenter.unsubscribe();}用于关联View,PresentermTasksView.setPresenter(tasksPresenter); TasksRepository获取Tasks 12345678910111213141516171819202122@Overridepublic Flowable<List<Task>> getTasks() { // Respond immediately with cache if available and not dirty if (mCachedTasks != null && !mCacheIsDirty) { return Flowable.fromIterable(mCachedTasks.values()).toList().toFlowable(); } else if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } Flowable<List<Task>> remoteTasks = getAndSaveRemoteTasks(); if (mCacheIsDirty) { return remoteTasks; } else { // Query the local storage if available. If not, query the network. Flowable<List<Task>> localTasks = getAndCacheLocalTasks(); return Flowable.concat(localTasks, remoteTasks) .filter(tasks -> !tasks.isEmpty()) .firstOrError() .toFlowable(); } } TasksPresenter中获取数据 123456789101112131415161718192021222324252627282930313233mCompositeDisposable.clear();Disposable disposable = mTasksRepository .getTasks() .flatMap(Flowable::fromIterable) .filter(task -> { switch (mCurrentFiltering) { case ACTIVE_TASKS: return task.isActive(); case COMPLETED_TASKS: return task.isCompleted(); case ALL_TASKS: default: return true; } }) .toList() .subscribeOn(mSchedulerProvider.io()) .observeOn(mSchedulerProvider.ui()) .doFinally(() -> { if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) { EspressoIdlingResource.decrement(); // Set app as idle. } }) .subscribe( // onNext tasks -> { processTasks(tasks); mTasksView.setLoadingIndicator(false); }, // onError throwable -> mTasksView.showLoadingTasksError()); mCompositeDisposable.add(disposable); 处理返回数据,更新UI 1234567891011121314151617181920TasksPresenter.javaprivate void processTasks(@NonNull List<Task> tasks) { if (tasks.isEmpty()) { // Show a message indicating there are no tasks for that filter type. processEmptyTasks(); } else { // Show the list of tasks mTasksView.showTasks(tasks); // Set the filter label's text. showFilterLabel(); } }TasksFragment.java@Overridepublic void showTasks(List<Task> tasks) { mListAdapter.replaceData(tasks); mTasksView.setVisibility(View.VISIBLE); mNoTasksView.setVisibility(View.GONE);}]]></content>
</entry>
<entry>
<title><![CDATA[Kotlin 入门]]></title>
<url>%2F2018%2F03%2F09%2FKotlin%E5%85%A5%E9%97%A8%2F</url>
<content type="text"><![CDATA[导入布局中所有控件属性 1import kotlinx.android.synthetic.main.<布局>.* 实验模式包含LayoutContainer`Parcelable` 1234567891011androidExtensions { experimental = true}import kotlinx.android.extensions.LayoutContainerclass ViewHolder(override val containerView: View) : ViewHolder(containerView), LayoutContainer { fun setup(title: String) { itemTitle.text = "Hello World!" }} View缓存策略,默认为HASH_MAP,findViewById()被调用一次 1234567891011androidExtensions { defaultCacheImplementation = "HASH_MAP" // also SPARSE_ARRAY, NONE}可以通过注解改变策略@ContainerOptions(cache = CacheImplementation.NO_CACHE)fun MyActivity.a() { // findViewById() will be called twice textView.text = "Hidden view" textView.visibility = View.INVISIBLE} Parcelable 123456789101112131415161718import kotlinx.android.parcel.Parcelize@Parcelizeclass User(val firstName: String, val lastName: String, val age: Int): Parcelable序列化读写@Parcelizedata class Value(val firstName: String, val lastName: String, val age: Int) : Parcelable { private companion object : Parceler<User> { override fun User.write(parcel: Parcel, flags: Int) { // Custom write implementation } override fun create(parcel: Parcel): User { // Custom read implementation } }} @Parcelize支持类型 自定义序列号映射对象 123456789class ExternalClass(val value: Int)object ExternalClassParceler : Parceler<ExternalClass> { override fun create(parcel: Parcel) = ExternalClass(parcel.readInt()) override fun ExternalClass.write(parcel: Parcel, flags: Int) { parcel.writeInt(value) }} 使用@TypeParceler @WriteWith引用自定义序列号映射对象 123456789101112// Class-local parceler@Parcelable@TypeParceler<ExternalClass, ExternalClassParceler>()class MyClass(val external: ExternalClass)// Property-local parceler@Parcelableclass MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass)// Type-local parceler@Parcelableclass MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass)]]></content>
<categories>
<category>Kotlin</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Kotlin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MVVM]]></title>
<url>%2F2018%2F03%2F09%2FMVVM%2F</url>
<content type="text"><![CDATA[Todo-mvvm-databinding Model: 内存,本地,网络数据的实现与获取 View: 对应Activity,Fragment,负责View的绘制,与用户交互,在xml中编写databinding对model层数据的引用绑定,ViewModelHolder<VH>作为非UI的Fragment持有ViewModel,并绑定到Activity的生命周期 ViewModel: databinding框架绑定ViewModel,ViewModel持有View中引用的变量observable fields,以及数据管理类TasksRepository,数据或属性发生变化时,databinding自动更新UI 优缺点 低耦合,ViewModel的逻辑代码可重用性,解决MVP中,View,Presenter相互持有问题,响应界面操作无需由View传递,数据变换也无需Presenter调用View实现,开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。可测试,界面素来是比较难于测试的,而现在测试可以针对ViewModel来写 ViewModel存在Model的依赖,databinding不方便调试 代码分析TasksActivity,绑定TasksFragment以及ViewModelHolder(持有ViewModel的非UI的Fragment,绑定到Activity的生命周期) 获取TasksViewModel,并绑定到Activity 12345678910111213141516171819202122private TasksViewModel findOrCreateViewModel() { @SuppressWarnings("unchecked") ViewModelHolder<TasksViewModel> retainedViewModel = (ViewModelHolder<TasksViewModel>) getSupportFragmentManager() .findFragmentByTag(TASKS_VIEWMODEL_TAG); if (retainedViewModel != null && retainedViewModel.getViewmodel() != null) { // If the model was retained, return it. return retainedViewModel.getViewmodel(); } else { // There is no ViewModel yet, create it. TasksViewModel viewModel = new TasksViewModel( Injection.provideTasksRepository(getApplicationContext()), getApplicationContext()); // and bind it to this Activity's lifecycle using the Fragment Manager. ActivityUtils.addFragmentToActivity( getSupportFragmentManager(), ViewModelHolder.createContainer(viewModel), TASKS_VIEWMODEL_TAG); return viewModel; } } 绑定Fragment和View,设置Activity的跳转 123456789101112131415 public interface TasksNavigator { void addNewTask();} mViewModel = findOrCreateViewModel(); mViewModel.setNavigator(this); // Link View and ViewModel tasksFragment.setViewModel(mViewModel); TasksViewModelvoid setNavigator(TasksNavigator navigator) { mNavigator = navigator;} 在onDestory()回收设置的跳转接口 1234567891011 @Override protected void onDestroy() { mViewModel.onActivityDestroyed(); super.onDestroy(); } TasksViewModelvoid onActivityDestroyed() { // Clear references to avoid potential memory leaks. mNavigator = null;} TasksFragment,持有TasksViewModel,TasksFragBinding,TasksAdapter中持有TaskItemViewModel 12345678910111213141516171819202122@Overridepublic void onResume() { super.onResume(); mTasksViewModel.start();}@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mTasksFragBinding = TasksFragBinding.inflate(inflater, container, false); mTasksFragBinding.setView(this); mTasksFragBinding.setViewmodel(mTasksViewModel); setHasOptionsMenu(true); View root = mTasksFragBinding.getRoot(); return root;} 设置SnackBar text属性的监听回调,属性改变时显示SnackBar,在onDestory()中移除回调 123456789101112131415161718 private void setupSnackbar() { mSnackbarCallback = new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable observable, int i) { SnackbarUtils.showSnackbar(getView(), mTasksViewModel.getSnackbarText()); } }; mTasksViewModel.snackbarText.addOnPropertyChangedCallback(mSnackbarCallback); } @Overridepublic void onDestroy() { mListAdapter.onDestroy(); if (mSnackbarCallback != null) { mTasksViewModel.snackbarText.removeOnPropertyChangedCallback(mSnackbarCallback); } super.onDestroy(); } 单个任务的抽象类TaskViewModel,暴露Task的观察者,设置标题,描述,Task,以及SnackBarText 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 public abstract class TaskViewModel extends BaseObservable implements TasksDataSource.GetTaskCallback { public final ObservableField<String> snackbarText = new ObservableField<>(); public final ObservableField<String> title = new ObservableField<>(); public final ObservableField<String> description = new ObservableField<>(); private final ObservableField<Task> mTaskObservable = new ObservableField<>(); private final TasksRepository mTasksRepository; private final Context mContext; private boolean mIsDataLoading; public TaskViewModel(Context context, TasksRepository tasksRepository) { mContext = context.getApplicationContext(); // Force use of Application Context. mTasksRepository = tasksRepository; // Exposed observables depend on the mTaskObservable observable: mTaskObservable.addOnPropertyChangedCallback(new OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable observable, int i) { Task task = mTaskObservable.get(); if (task != null) { title.set(task.getTitle()); description.set(task.getDescription()); } else { title.set(mContext.getString(R.string.no_data)); description.set(mContext.getString(R.string.no_data_description)); } } }); } public void start(String taskId) { if (taskId != null) { mIsDataLoading = true; mTasksRepository.getTask(taskId, this); } } public void setTask(Task task) { mTaskObservable.set(task); } // "completed" is two-way bound, so in order to intercept the new value, use a @Bindable // annotation and process it in the setter. @Bindable public boolean getCompleted() { Task task = mTaskObservable.get(); return task != null && task.isCompleted(); } public void setCompleted(boolean completed) { if (mIsDataLoading) { return; } Task task = mTaskObservable.get(); // Notify repository and user if (completed) { mTasksRepository.completeTask(task); snackbarText.set(mContext.getResources().getString(R.string.task_marked_complete)); } else { mTasksRepository.activateTask(task); snackbarText.set(mContext.getResources().getString(R.string.task_marked_active)); } } @Bindable public boolean isDataAvailable() { return mTaskObservable.get() != null; } @Bindable public boolean isDataLoading() { return mIsDataLoading; } // This could be an observable, but we save a call to Task.getTitleForList() if not needed. @Bindable public String getTitleForList() { if (mTaskObservable.get() == null) { return "No data"; } return mTaskObservable.get().getTitleForList(); } @Override public void onTaskLoaded(Task task) { mTaskObservable.set(task); mIsDataLoading = false; notifyChange(); // For the @Bindable properties } @Override public void onDataNotAvailable() { mTaskObservable.set(null); mIsDataLoading = false; } public void deleteTask() { if (mTaskObservable.get() != null) { mTasksRepository.deleteTask(mTaskObservable.get().getId()); } } public void onRefresh() { if (mTaskObservable.get() != null) { start(mTaskObservable.get().getId()); } } public String getSnackbarText() { return snackbarText.get(); } @Nullable protected String getTaskId() { return mTaskObservable.get().getId(); }} TasksItemViewModel,持有Navigator的弱引用,在task_item.xml中,绑定点击事件android:onClick="@{() -> viewmodel.taskClicked()}",以及Task激活状态android:checked="@={viewmodel.completed}",标题android:text="@{viewmodel.titleForList}" 1234567891011121314151617181920212223242526272829 public class TaskItemViewModel extends TaskViewModel { // This navigator is s wrapped in a WeakReference to avoid leaks because it has references to an // activity. There's no straightforward way to clear it for each item in a list adapter. @Nullable private WeakReference<TaskItemNavigator> mNavigator; public TaskItemViewModel(Context context, TasksRepository tasksRepository) { super(context, tasksRepository); } public void setNavigator(TaskItemNavigator navigator) { mNavigator = new WeakReference<>(navigator); } /** * Called by the Data Binding library when the row is clicked. */ public void taskClicked() { String taskId = getTaskId(); if (taskId == null) { // Click happened before task was loaded, no-op. return; } if (mNavigator != null && mNavigator.get() != null) { mNavigator.get().openTaskDetails(taskId); } }} TasksViewModel,主要是任务列表的数据加载,UI更新 123456789101112131415161718192021222324252627282930313233343536public class TasksViewModel extends BaseObservable { // These observable fields will update Views automatically public final ObservableList<Task> items = new ObservableArrayList<>(); public final ObservableBoolean dataLoading = new ObservableBoolean(false); public final ObservableField<String> currentFilteringLabel = new ObservableField<>(); public final ObservableField<String> noTasksLabel = new ObservableField<>(); public final ObservableField<Drawable> noTaskIconRes = new ObservableField<>(); public final ObservableBoolean tasksAddViewVisible = new ObservableBoolean(); final ObservableField<String> snackbarText = new ObservableField<>(); private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS; private final TasksRepository mTasksRepository; private final ObservableBoolean mIsDataLoadingError = new ObservableBoolean(false); private Context mContext; // To avoid leaks, this must be an Application Context. private TasksNavigator mNavigator; public TasksViewModel( TasksRepository repository, Context context) { mContext = context.getApplicationContext(); // Force use of Application Context. mTasksRepository = repository; // Set initial state setFiltering(TasksFilterType.ALL_TASKS);} tasks_frag.xml中绑定TasksFragment,TasksViewModel 12345678910111213<data> <import type="android.view.View" /> <variable name="view" type="com.example.android.architecture.blueprints.todoapp.tasks.TasksFragment" /> <variable name="viewmodel" type="com.example.android.architecture.blueprints.todoapp.tasks.TasksViewModel" /> </data> 自定义设置可滑动子View 12345678910111213141516171819202122232425ublic class ScrollChildSwipeRefreshLayout extends SwipeRefreshLayout { private View mScrollUpChild; public ScrollChildSwipeRefreshLayout(Context context) { super(context); } public ScrollChildSwipeRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean canChildScrollUp() { if (mScrollUpChild != null) { return mScrollUpChild.canScrollVertically(-1);// return ViewCompat.canScrollVertically(mScrollUpChild, -1); } return super.canChildScrollUp(); } public void setScrollUpChild(View view) { mScrollUpChild = view; }} ScrollChildSwipeRefreshLayout刷新时加载方法android:onRefresh="@{viewmodel}",自定义setter 12345678910111213public class SwipeRefreshLayoutDataBinding {@BindingAdapter("android:onRefresh")public static void setSwipeRefreshLayoutOnRefreshListener(ScrollChildSwipeRefreshLayout view, final TasksViewModel viewModel) { view.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { viewModel.loadTasks(true); } }); }} app:items="@{viewmodel.items}"替换ListView的items 1234567891011public class TasksListBindings { @SuppressWarnings("unchecked") @BindingAdapter("app:items") public static void setItems(ListView listView, List<Task> items) { TasksFragment.TasksAdapter adapter = (TasksFragment.TasksAdapter) listView.getAdapter(); if (adapter != null) { adapter.replaceData(items); } }} TasksAapter,TasksItemBinding绑定ViewModel,ListView优化策略达到重复利用Binding,在item的snackbarText发生改变时,改变TasksViewModel的snackbarText,然后显示SnackBar的提示信息 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586public static class TasksAdapter extends BaseAdapter { @Nullable private TaskItemNavigator mTaskItemNavigator; private final TasksViewModel mTasksViewModel; private List<Task> mTasks; private TasksRepository mTasksRepository; public TasksAdapter(List<Task> tasks, TasksActivity taskItemNavigator, TasksRepository tasksRepository, TasksViewModel tasksViewModel) { mTaskItemNavigator = taskItemNavigator; mTasksRepository = tasksRepository; mTasksViewModel = tasksViewModel; setList(tasks); } public void onDestroy() { mTaskItemNavigator = null; } public void replaceData(List<Task> tasks) { setList(tasks); } @Override public int getCount() { return mTasks != null ? mTasks.size() : 0; } @Override public Task getItem(int i) { return mTasks.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { Task task = getItem(i); TaskItemBinding binding; if (view == null) { // Inflate LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); // Create the binding binding = TaskItemBinding.inflate(inflater, viewGroup, false); } else { // Recycling view binding = DataBindingUtil.getBinding(view); } final TaskItemViewModel viewmodel = new TaskItemViewModel( viewGroup.getContext().getApplicationContext(), mTasksRepository ); viewmodel.setNavigator(mTaskItemNavigator); binding.setViewmodel(viewmodel); // To save on PropertyChangedCallbacks, wire the item's snackbar text observable to the // fragment's. viewmodel.snackbarText.addOnPropertyChangedCallback( new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable observable, int i) { mTasksViewModel.snackbarText.set(viewmodel.getSnackbarText()); } }); viewmodel.setTask(task); return binding.getRoot(); } private void setList(List<Task> tasks) { mTasks = tasks; notifyDataSetChanged(); } }]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Android Notes(Performance)]]></title>
<url>%2F2018%2F03%2F08%2FAndroid-Notes-Performance%2F</url>
<content type="text"><![CDATA[Performance optimization布局 人眼感觉流畅画面的帧数要达到40-60帧每秒 在Android中,系统通过VSYNC信号触发对UI的渲染,重绘,需要16ms,其实就是1000ms中显示60帧,1000/60=16.67.如果不能在16ms完成绘制,就会丢帧,例如绘制耗时20ms,在16ms有VSYNC信号时无法绘制,该帧被丢弃,等待下次信号才开始绘制,导致16*2ms 都显示同一帧画面 在开发者选项中有Profile GPU Rendering 选中On Screen as bars,中间的绿色横线代表VSYNC时间16ms柱状线包含3个部分 蓝色代表测量绘制Display List的时间 红色代表OpenGL渲染Display List的时间 黄色代表CPU等待GPU处理的时间Android 6.0 OverdrawEnable GPU Overdraw 查看当前区域绘制次数,增大蓝色区域,减少红色区域 移除不需要背景 减少布局层级,降低view树高度,不应超过10层 12<include> 布局中不设置宽高具体值(0dp)<ViewStub> 延时加载,可以使用setVisibility(),inflate()显示View,之后ViewStub就不存在了,被显示的layout代替,不能inflate两次,而View.GONE会在初始化布局的时候就已经添加在布局树中,相比之下ViewStub更有效率 减少使用透明度alpha(会先绘制有颜色,再设置透明度) hierarchy viewer 1234"Tools" menu > Android > Android Device MonitorIn ADM: "Window" Menu > PerspectiveClick on Hierarchy Viewer.Profile Node:Obtain layout times at the top of the Tree View 内存 内存太低出发LMK(Low Memory Killer) 手机内存指RAM,包括 12345寄存器(Registers) 位于处理器内部,速度最快,程序无法控制栈 存放基本类型的数据和对象的引用,对象本身不存放在栈中,而是存放在堆中堆 存放对象和数组,堆中分配的内存由Java虚拟机的回收器GC来管理静态存储区域 存放应用程序运行时一直存在的数据,如静态的数据变量常量池 常量的有序集合,包括基本类型,String和对其他类型,字段,方法的符合引用 当定义一个变量,会在栈中分配内存空间,作用域结束后,会立即被用作新的空间分配。当new一个变量,在堆中分配内存空间,该对象作用域结束后,内存也不会立即回收,等待系统GC回收,内存分析就是分析Heap中的内存状态 12ActivityManager am=getSystemService(Context.ACTIVITY_SERVICE);int heapSize=am.getLargeMemoryClass(); 获取系统内存信息 1adb shell dumpsys procstats 内存监视,Settings-Apps-Running 1adb shell dumpsys meminfo Bitmap优化 列表页面使用缩略图,低质量图片 使用完Bitmap,使用bitmap.recycle()释放资源,Android3.0后Bitmap存放在堆内存中,内存由GC管理,不需要进行释放了 使用内存缓存(LruCache)和硬盘缓存(DiskLruCache)可以更好使用Bitmap 代码优化 常量使用static修饰符 静态方法比普通方法提高15%左右访问速度 减少不必要的成员变量 减少不必要的对象,使用基础类型比使用对象更加节省资源 尽量不适应枚举,少使用迭代器 对Cursor,Receiver,Sensor,File 非常注意对他们的创建,回收,注册,解注册 避免使用IOC框架,IOC通常使用注解,反射实现 使用RenderScript,OpenGL来进行非常复杂的绘图操作 使用SurfaceView 来代替View进行大量的绘图操作 尽量使用绘图缓存,而不是inflate()方法解析视图 工具分析优化 Lint 工具优化代码 Android Profile 监测CPU,Memory,Network TraceView 优化App Debug类开启TarceView 1234<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>onCreate()->Debug.startMethodTracing()onDestory()->Debug.stopMethodTracing()adb pull /sdcard/trace_log.trace/local/LOG TraceView日志 Incl CPU Time 某方法占用CPU的时间 Excl CPU Time 某方法本身不包括子方法占用CPU的时间 Incl Real Time 某方法真正执行的时间 Excl Real Time某方法本身不包括子方法执行的时间 Calls+RecurCalls 调用次数+递归回调次数 从Incl CPU Time和Calls+RecurCalls开始进行分析,如果占用时间长,Calls+RecurCalls次数少,就可以是怀疑对象了 MAT 手动查看Heap,判断是否有内存泄漏小技巧:不停点击Cause GC,如果data object的total size有明显变化,就可能有内存泄漏 点击Dump HPROF File,生成.hprof,然后再使用命令转换1hprof-conv com.willkernel.app.audiobar.hprof heap.hprof Dumpsys分析系统状态12345678activity 显示所有Activity栈的信息meminfo 内存信息battery 电池信息package包信息wifi WiFi信息alarm alarm信息procstats 内存状态grep,find 在性能优化bug分析时有用]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Android Notes(System Info Manager Safety)]]></title>
<url>%2F2018%2F03%2F08%2FAndroid-Notes-System-Info-Manager-Safety%2F</url>
<content type="text"><![CDATA[Android System Info android.os.Build12345678910111213141516171819202122Build.BOARD 主板Build.BRAND 系统定制商Build.SUPPORTED_ABIS CPU指令集Build.DEVICE 设备参数Build.DISPLAY 显示屏参数Build.FINGERPRINT 唯一编号Build.SERIAL 硬件序列号Build.ID 修订版本Build.MANUFACTURER 硬件制造商Build.MODEL 版本Build.HARDWARE 硬件名Build.PRODUCT 手机产品名Build.TAGS Build 标签Build.TYPE Build 类型Build.VERSION.CODENAME 开发代号Build.VERSION.INCREMENTAL 源码控制版本号Build.VERSION.RELEASE 版本字符串Build.VERSION.SDK_INT 版本号// The following properties only make sense for internal engineering builds.Build.HOST Host值Build.USER User名Build.TIME 编译时间 SystemProperty 1234567891011121314151617String getPropertyos.version os版本os.name os名称os.arch os架构user.home Home属性user.name Name属性user.dir Dir属性user.timezone 时区path.separator 路径分隔符line.separator 行分隔符file.separator 文件分隔符java.vendor.url Java vendor url属性java.class.path Java Class属性java.class.version Java Class版本java.vendor Java vendor属性java.version Java版本java.home Java Home属性 应用 123456789101112131415String board=Build.BOARDString brand=Build.BRANDString os_version=System.getProperty("os.version")String os_name=System.getProperty("os.name")cd system/cat build.prop 查看build信息getprop ro.build.id 获取对应属性值MRA58Kcd proc/ls -al 查看文件详细信息cat cpuinfo 查看cpu信息 PackageManager(应用包信息) 123456789getPackageManager 返回PackageManager对象getApplicationInfo 返回指定包名的ApplicationInfogetApplicationIcon 返回指定包名的IcongetInstalledApplication 以ApplicationInfo形式返回安装的应用getInstalledPackages 以PackageInfo形式返回安装的应用queryIntentActivities 返回指定intent的ResolveInfo对象,Activity集合queryIntentServices 返回指定intent的ResolveInfo对象,Service集合resolveActivity 返回指定intent的ActivityresolveService 返回指定intent的Service 判断APP类型 12345 app.flags&ApplicationInfo.FLAG_SYSTEM!=0为系统应用 app.flags&ApplicationInfo.FLAG_SYSTEM<=0则为第三方应用 特殊情况,系统应用升级后也成为第三方应用 app.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP!=0 app.flags&ApplicationInfo.FLAG_EXTERNAL_STORAGE!=0为安装在SDCard应用List<ApplicationInfo> list=pm.getInstalledApplications(PackManager.GET_UNINSTALLED_PACKAGES/MATCH_UNINSTALLED_PACKAGES); ActivityManager(运行应用程序信息)123456789101112131415161718192021ActivityManager.MemoryInfo 全局内存使用信息availMem 系统可用内存totalMem 总内存threshold 低内存的阈值lowMemory 是否处于低内存Debug.MemoryInfo 进程下的内存信息RunningAppProcessInfoprocessName 进程名pid 进程piduid 进程uidpkgList 该进程下所有包RunningServiceInfoactiveSince 第一次激活的时间,方式foreground 服务是否在前台运行List<RunningAppProcessInfo> list=am.getRunningAppProcess();Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) 查看packages.xml获取系统信息1adb pull data/system/packages.xml 123codepath apk安装路径system/app 存放系统或厂商定制apkdata/app 存放第三方apk Android安全 代码混淆proguard,混淆代码,替换命名,压缩代码,优化编译后的字节码 AndroidManifest 权限声明,权限检查 1234检测操作者权限判断permission名称,如果为空,直接返回DENIED判断UID,如果为0,则为Root权限,不做权限控制;如果为system server的uid,不做权限控制;如果uid与参数中的请求uid不同,则返回DENIEDPackageManagerService.checkUidPermission()判断uid是否有相应的权限,系统级的platform.xml中进行查找 数字证书,APP签名文件 Linux内核访问权限控制,System,root用户才有权限访问系统文件 虚拟机沙箱隔离,每个APP有与之对应的UID 隐患 123代码漏洞:LaunchAnyWhere,FakeID BugRoot风险安全机制不健全 反编译工具 apktool 1234反编译 apktool.jar d test.apk生成test文件夹重新打包 apktool.jar b test.apk生成重新打包的apk,在dist文件夹中 Dex2jar,jd-gui 123dex2jar.bat classes.dex生成classes.jar文件使用jd-gui查看源码 apk加密 1234567buildTypes { release{ minifyEnabled false //以前属性名是runProguard proguardFiles getDefaultProguardFile('proguard-android.txt'), //系统默认 'proguard-rules.pro' // 项目自定义 }} 加固 阿里聚安全 腾讯云应用乐固 360加固保]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Android Notes(Activity)]]></title>
<url>%2F2018%2F03%2F07%2FAndroid-Notes-Activity%2F</url>
<content type="text"><![CDATA[Activity,Activity调用栈 onCreate 创建基本元素 初始化资源,或在onPause中释放的资源 onPause/onStop 清除Activity资源,camera,sensor,receivers onDestory 清除线程 activity 在onSaveInstanceState()保存状态到Bundle,在onRestoreInstanceState(),onCreate()恢复状态Bundle,已经默认实项了控件的状态保存 Activity任务栈 栈底元素是整个任务栈发起者 一个任务栈Task,表示若干个Activity集合,可以来自不同的App,一个App的Activity也可以在不同的Task中 LIFO 后进先出 启动模式launchmode standard默认启动模式,每次都会创建新的实例 singleTop栈顶模式,如果栈顶是要启动的Activity,直接引用,如果不是创建新的Activity,例如qq接受多条消息,弹出的消息界面 singleTask单栈模式,要启动的Activity,如果栈中存在该Activity,将其置于栈顶,并将位于上方的Activity销毁,指同一个App中启动它的Activity,如果其他App以singleTask模式启动这个Activity,将会创建新的Activity。但是如果启动的Activity已经在一个任务栈中处于后台,那所在的任务栈会和要启动的Activity一起回到前台,当已经启动的Activity需要按返回键时,就会先返回Activity所在栈的元素应用:将主Activity设置为singleTask,然后在要退出的Activity中启动主Activity,将他之上的Activity全部销毁,重写onNewIntent(),加上finish(),将最后一个主Activity结束 singleInstanse栈中只存在该一个Activity,共享实例,不需要重新创建,类似浏览器,电话 注意:singleTop或singleInstance的Activity A通过startActivityForResult()启动另一个Activity B,直接返回Activity.RESULT_CANCELD ,只能通过Intent绑定数据 Intent Flag Intent.FLAG_ACTIVITY_NEW_TASK 启动的Activity在新的Task中,使用在Service中启动Activity,因为Service没有任务栈 Intent.FLAG_ACTIVITY_SINGLE_TOP 存在直接引用,不存在创建新的 Intent.FLAG_ACTIVITY_CLEAR_TOP 与singleTask相同,存在引用并销毁,不存在创建新的 Intent.FLAG_ACTIVITY_NO_HISTORY 以这种模式启动的Activity C会消失,A->B->C->D,当前Activity栈中为ABD 清空任务栈 clearTaskOnTouch 每次返回该Activity 清除其他Activity,初始化Task,只有一个该Activity finishOnTaskLaunch 返回该Activity时,会被finish alwaysRetainTaskState 保持当前Task状态,不接受任何清理命令]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Android Notes(SurfaceView)]]></title>
<url>%2F2018%2F03%2F07%2FAndroid-Notes-SurfaceView%2F</url>
<content type="text"><![CDATA[SurfaceView View适用于主动刷新,SurfaceView适用于被动刷新,例如频繁刷新 View在主线程中对画面进行刷新,SurfaceView通常会在子线程进行页面刷新 View绘制时没有使用双缓冲机制,SurfaceView在底层机制中实项了双缓冲机制 正弦曲线,绘画板1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 private void init() { mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true);// mSurfaceHolder.setFormat(PixelFormat.OPAQUE); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPath = new Path(); mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mDrawPaint.setColor(Color.RED); mDrawPaint.setStyle(Paint.Style.STROKE); mDrawPaint.setStrokeCap(Paint.Cap.ROUND); mDrawPaint.setStrokeWidth(5); mDrawPaint.setStrokeJoin(Paint.Join.ROUND); mDrawPath = new Path(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mPath.moveTo(0, getHeight() / 2); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; Log.e("surfaceCreated", Thread.currentThread().getName()); new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDrawPath.moveTo(x, y); break; case MotionEvent.ACTION_MOVE: mDrawPath.lineTo(x, y); break; case MotionEvent.ACTION_UP: break; } return true; } @Override public void run() { while (mIsDrawing) { long start = System.currentTimeMillis(); draw(); long end = System.currentTimeMillis(); //100是经验值,取值范围50-100ms if (end - start < 100) { try { Thread.sleep(100 - (end - start)); } catch (InterruptedException e) { e.printStackTrace(); } } x += 1; y = (int) (100 * Math.sin(x * 2 * Math.PI / 180) + 100); mPath.lineTo(x, y); } } private void draw() { try { //获得canvas图像 mCanvas = mSurfaceHolder.lockCanvas(); //surfaceview 背景 mCanvas.drawColor(Color.WHITE); //绘制路径 mCanvas.drawPath(mPath, mPaint); mCanvas.drawPath(mDrawPath, mDrawPaint); } catch (Exception e) { e.printStackTrace(); } finally { //保证每次内容的提交 if (mCanvas != null) mSurfaceHolder.unlockCanvasAndPost(mCanvas); } }]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Android Notes(Paint Shader)]]></title>
<url>%2F2018%2F03%2F07%2FAndroid-Notes-Paint-Shader%2F</url>
<content type="text"><![CDATA[Paint PorterDuffXfermode 两个图层交集区域显示方式,用的最多是DST_IN,SRC_IN显示圆形图片或圆角图片 圆角矩形 123456789101112private void drawRoundRect() { ImageView roundRectIv = findViewById(R.id.roundRectIv); Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.car3); Bitmap mOut = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888); Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); PorterDuffXfermode mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); Canvas canvas = new Canvas(mOut); canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), 100, 100, mPaint); mPaint.setXfermode(mXfermode); canvas.drawBitmap(mBitmap, 0, 0, mPaint); roundRectIv.setImageBitmap(mOut); } 刮刮卡12345678910111213141516171819202122232425262728293031323334353637383940private void init() { bgBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.car3); fgBmp = Bitmap.createBitmap(bgBmp.getWidth(), bgBmp.getHeight(), Bitmap.Config.ARGB_8888); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //画路径时走透明通道,形成刮刮卡效果 mPaint.setAlpha(0); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeWidth(50); mCanvas = new Canvas(fgBmp); mCanvas.drawColor(Color.GRAY); mPath = new Path(); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.reset(); mPath.moveTo(x, y); break; case MotionEvent.ACTION_MOVE: mPath.lineTo(x, y); break; } mCanvas.drawPath(mPath, mPaint); invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(bgBmp, 0, 0, null); canvas.drawBitmap(fgBmp, 0, 0, null); } Shader(着色器,渲染器)123CLAMP 拉伸图片最后一个像素,不断重复REPEAT 横向纵向不断重复MIRROR 横向纵向翻转重复 BitmapShader 位图shader 12345678910111213141516171819202122232425圆形图片private void init() { bm = BitmapFactory.decodeResource(getResources(), R.mipmap.car3); //图像填充功能 bitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.REPEAT); mPaint = new Paint(); mPaint.setShader(bitmapShader); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); mLinearGradient=new LinearGradient(100,300,300,500, Color.RED,Color.YELLOW, Shader.TileMode.CLAMP); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(200, 200, 100, mPaint); mPaint.setShader(mLinearGradient); canvas.drawRect(100,300,300,500,mPaint); } LinearGradient 线性shader RadialGradient 光束shader SweepGradient 梯度shader ComposeShader 混合shader 图像倒影 1234567891011121314151617181920212223242526private void init() { mSrcBitmap= BitmapFactory.decodeResource(getResources(), R.mipmap.car3); Matrix matrix=new Matrix(); //实项垂直翻转,(-1,1)实项水平翻转 matrix.setScale(1,-1); mRefBitmap=Bitmap.createBitmap(mSrcBitmap,0,0, mSrcBitmap.getWidth(),mSrcBitmap.getHeight(),matrix,true); mPaint=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setShader(new LinearGradient(0,mSrcBitmap.getHeight(), 0,mSrcBitmap.getHeight()+mSrcBitmap.getHeight()/4, 0xdd000000,0x33000000, Shader.TileMode.CLAMP)); mXferMode=new PorterDuffXfermode(PorterDuff.Mode.DST_IN); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLACK); canvas.drawBitmap(mSrcBitmap,0,0,null); canvas.drawBitmap(mRefBitmap,0,mSrcBitmap.getHeight(),null); mPaint.setXfermode(mXferMode); canvas.drawRect(0,mSrcBitmap.getHeight(),mRefBitmap.getWidth(), mSrcBitmap.getHeight()*2,mPaint); mPaint.setXfermode(null); } PathEffect ComposePathEffect 先应用一种路径效果,再这个基础上混合另外一种效果 CornerPathEffect 圆角路径 DashPathEffect 虚线路径 DiscretePathEffect 杂点路径 PathDashPathEffect 设置点的图形路径效果 SumPathEffect 组合两种路径后再应用到图形上 12345678910111213141516171819202122232425262728293031private void init() { mEffects=new PathEffect[7]; mPaint=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(4); mPath=new Path(); mPath.moveTo(0,0); for (int i = 0; i <=30; i++) { mPath.lineTo(i*35, (float) (Math.random()*100)); } mEffects[0]=null; mEffects[1]=new CornerPathEffect(30); mEffects[2]=new DiscretePathEffect(5,3); mEffects[3]=new DashPathEffect(new float[]{10,20,30,50},0); Path path=new Path(); path.addRect(0,0,10,10, Path.Direction.CCW); mEffects[4]=new PathDashPathEffect(path,12,0, PathDashPathEffect.Style.ROTATE); mEffects[5]=new ComposePathEffect(mEffects[3],mEffects[1]); mEffects[6]=new SumPathEffect(mEffects[3],mEffects[1]); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (PathEffect mEffect : mEffects) { mPaint.setPathEffect(mEffect); canvas.drawPath(mPath,mPaint); canvas.translate(0,200); } }]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Android Notes(View)]]></title>
<url>%2F2018%2F03%2F05%2FAndroid-Notes-View%2F</url>
<content type="text"><![CDATA[Android 绘图机制处理 系统屏幕密度,密度值,分辨率对应关系 1ldpi=120(240*320),mdpi=160(320*480),hdpi=240(480*800),xhdpi=320(720*1280),xxhdpi=480(1080*1980) 独立像素密度(密度无关像素 (dp)在定义 UI 布局时应使用的虚拟像素单位,用于以密度无关方式表示布局维度 或位置) 1ldpi:mdpi:hdpi:xhdpi:xxhdpi=120:160:240:320:480=0.75px:1px:1.5px:2px:3px=3:4:6:8:12 单位转换 12345678910scale=getResources().getDisplayMetrics().density;px2dp: pxValue/scale+0.5f;dp2px: dpValue*scale+0.5f;fontScale=getResources().getDisplayMetrics().scaledDensity;px2sp: pxValue/fontScale+0.5f;sp2px: pxValue*fontScale+0.5f;TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResource().getDisplayMetrics()); 2D绘图 setAntiAlias()//画笔锯齿 setColor()//画笔颜色 setARGB() setAlpha() setTextSize() setStyle()//空心,实心 setStrokerWidth()//边框宽度1234567891011drawPointdrawLinedrawLinesdrawRectdrawRoundRectdrawCircledrawArc 绘制弧形,使用中心或实心,空心drawOval 绘制椭圆drawText 绘制文本drawPosText 指定位置绘制文本drawPath 绘制路径 XML Bitmap 12<bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:src="@mipmap/car1" /> Shape 123456789101112131415<shape android:shape="rectangle|oval|line|ring"><!--配合ImageView scaleType--><size android:width="12dp" android:height="12dp" /><!--虚线宽度,间隔--><stroke android:width="20dp" android:color="@color/colorAccent" android:dashGap="2dp" android:dashWidth="6dp" /></shape> Layer 1234<layer-list<item android:drawable="@mipmap/car1"/><item android:drawable="@mipmap/car2"/></layer-list> Selector 1234567891011<item android:drawable="@android:drawable/btn_default" /> <!--没有焦点时--> <item android:state_window_focused="false" /> <!--非触摸单击时--> <item android:state_focused="true" android:state_pressed="true" /> <!--触摸模式下单击时--> <item android:state_focused="false" android:state_pressed="true" /> <!--选中时--> <item android:state_selected="true" /> <!--获得焦点时--> <item android:state_focused="true" /> 绘图技巧 Canvas 123456789//保存之前绘制图像,后续绘制在新的图层canvas.save()//save之前的图像和之后的图像合并canvas.restore() //坐标系平移,原点(0,0)移动到(x,y)canvas.translate()//坐标系旋转canvas.rotate() Board 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.RED); mPaint.setStrokeWidth(2);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = getWidth(); mHeight = getHeight();}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint); for (int i = 0; i < 24; i++) { if (i == 0 || i == 6 || i == 12 | i == 18) { mPaint.setStrokeWidth(5); mPaint.setTextSize(30); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 60, mPaint); canvas.drawText(String.valueOf(i), mWidth / 2, mHeight / 2 - mWidth / 2 + 90, mPaint); } else { mPaint.setStrokeWidth(3); mPaint.setTextSize(16); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 30, mPaint); canvas.drawText(String.valueOf(i), mWidth / 2, mHeight / 2 - mWidth / 2 + 60, mPaint); } //旋转画布简化角度,坐标运算 canvas.rotate(15, mWidth / 2, mHeight / 2); } canvas.save(); mPaint.setColor(Color.CYAN); mPaint.setStrokeWidth(10); canvas.translate(mWidth / 2, mHeight / 2); canvas.drawLine(0, 0, 100, 100, mPaint); canvas.drawLine(0, 0, 100, 200, mPaint); canvas.restore();} layer图层 1234567891011@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(Color.BLUE); canvas.drawCircle(mWidth / 2, mHeight / 2, 50, mPaint); canvas.saveLayerAlpha(0, 0, mWidth, mHeight, 127, ALL_SAVE_FLAG); mPaint.setColor(Color.RED); canvas.drawCircle(mWidth / 2 + 50, mHeight / 2 + 50, 50, mPaint); canvas.restore();} 色彩特效 图片的数据结构常使用位图Bitmap,由点阵(像素点矩阵)和颜色值(ARGB)组成 色彩处理中包含色调,饱和度,亮度,使用ColorMatrix(4*5颜色矩阵) 1234567891011121314151617181920212223242526272829303132333435[a, b, c, d, e,f, g, h, i, j,k, l, m, n, o,p, q, r, s, t ]在Android中以一维数组存储 float[] mArray = new float[20]R = a*R + b*G + c*B + d*A + e;G = f*R + g*G + h*B + i*A + j;B = k*R + l*G + m*B + n*A + o;A = p*R + q*G + r*B + s*A + t;初始矩阵不会对颜色改变A=[1,0,0,0,00,1,0,0,00,0,1,0,00,0,0,1,0]色调setRotate(int axis,floate degrees)axis=0 the RED coloraxis=1 the GREEN coloraxis=2 the BLUE color饱和度,为0时是灰度图像setSaturation(float sat)亮度setScale(float rScale, float gScale, float bScale,float aScale)矩阵乘法运算postConcat(ColorMatrix postmatrix)setConcat(postmatrix, this)imageMatrix.postContact(hueMatrix)imageMatrix.postContact(saturationMatrix)imageMatrix.postContact(lumMatrix) 滑动seekbar,改变颜色混合效果 123456789101112131415161718192021222324252627282930313233343536373839404142public static Bitmap handleImageEffect(Bitmap bm, float hue, float saturation, float lum) { Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); Paint paint = new Paint(); ColorMatrix hueMatrix = new ColorMatrix(); hueMatrix.setRotate(0, hue); hueMatrix.setRotate(1, hue); hueMatrix.setRotate(2, hue); ColorMatrix saturationMatrix = new ColorMatrix(); saturationMatrix.setSaturation(saturation); ColorMatrix lumMatrix = new ColorMatrix(); lumMatrix.setScale(lum, lum, lum, 1); ColorMatrix imageMatrix = new ColorMatrix(); imageMatrix.postConcat(hueMatrix); imageMatrix.postConcat(saturationMatrix); imageMatrix.postConcat(lumMatrix); paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix)); canvas.drawBitmap(bm, 0, 0, paint); return bmp; } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { switch (seekBar.getId()) { case R.id.hueSeekBar: mHue = (progress - 50) * 1.0f / 50 * 180; break; case R.id.saturationSeekBar: mSaturation = progress * 1.0f / 50; break; case R.id.lumSeekBar: mLum = progress * 1.0f / 50; break; } filterImage.setImageBitmap(ColorMatrixUtil.handleImageEffect(bm, mHue, mSaturation, mLum)); } Android不允许直接修改原图,创建同大小的位图,并将原图绘制到该Bitmap 1234Bitmap bmp=Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),Bitmap.Config.ARGB_8888);Canvas canvas=new Canvas(bmp);paint.setColorFilter(new ColorMatrixColorFilter(matrix));canvas.drawBitmap(bm,0,0,paint); ColorMatrix 1234567891011121314151617181920212223242526272829303132333435363738394041424344private void addEts() { gridLayout.post(new Runnable() { int mEtHeight; int mEtWidth; @Override public void run() { mEtHeight = gridLayout.getWidth() / 4; mEtWidth = gridLayout.getWidth() / 5; for (int i = 0; i < 20; i++) { EditText editText = new EditText(getBaseContext()); gridLayout.addView(editText, mEtWidth, mEtHeight); mEts[i] = editText; } initMatrix(); } }); } private void initMatrix() { for (int i = 0; i < mEts.length; i++) { if (i % 6 == 0) { mEts[i].setText("1"); } else { mEts[i].setText("0"); } } } private void getMatrix() { for (int i = 0; i < mEts.length; i++) { mColorMatrix[i] = Float.valueOf(mEts[i].getText().toString()); } } private void setImageMatrix() { Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); Paint paint = new Paint(); ColorMatrix colorMatrix = new ColorMatrix(mColorMatrix); paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); canvas.drawBitmap(bm, 0, 0, paint); filterImage.setImageBitmap(bmp); } 图像反转 获取图像像素值,三种像素点处理 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485private void getPixels() { ImageView pixelImage1 = findViewById(R.id.pixelImage1); ImageView pixelImage2 = findViewById(R.id.pixelImage2); ImageView pixelImage3 = findViewById(R.id.pixelImage3); bm = BitmapFactory.decodeResource(getResources(), R.mipmap.car3); int[] pixels = new int[bm.getWidth() * bm.getHeight()]; int[] newPixels1 = new int[bm.getWidth() * bm.getHeight()]; int[] newPixels2 = new int[bm.getWidth() * bm.getHeight()]; int[] newPixels3 = new int[bm.getWidth() * bm.getHeight()]; bm.getPixels(pixels, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); Bitmap bmp1 = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); Bitmap bmp2 = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); Bitmap bmp3 = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); for (int i = 0; i < pixels.length; i++) { int color = pixels[i]; int r = Color.red(color); int g = Color.green(color); int b = Color.blue(color); int a = Color.alpha(color); //老照片 int r1 = (int) (0.393 * r + 0.769 * g + 0.189 * b); int g1 = (int) (0.349 * r + 0.686 * g + 0.168 * b); int b1 = (int) (0.272 * r + 0.534 * g + 0.131 * b); newPixels1[i] = Color.rgb(r1, g1, b1); //底片处理 int r2 = 255 - r; int g2 = 255 - g; int b2 = 255 - b; if (r > 255) { r = 255; } else if (r < 0) { r = 0; } if (g > 255) { g = 255; } else if (g < 0) { g = 0; } if (b > 255) { b = 255; } else if (b < 0) { b = 0; } newPixels2[i] = Color.argb(a, r2, g2, b2); //浮雕 int rB = 0; int gB = 0; int bB = 0; if (i + 1 < pixels.length) { int colorB = pixels[i + 1]; rB = Color.red(colorB); gB = Color.green(colorB); bB = Color.blue(colorB); rB = r - rB + 127; gB = g - gB + 127; bB = b - bB + 127; if (rB > 255) { rB = 255; } if (gB > 255) { gB = 255; } if (bB > 255) { bB = 255; } } newPixels3[i] = Color.rgb(rB, gB, bB); } bmp1.setPixels(newPixels1, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); bmp2.setPixels(newPixels2, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); bmp3.setPixels(newPixels3, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); pixelImage1.setImageBitmap(bmp1); pixelImage2.setImageBitmap(bmp2); pixelImage3.setImageBitmap(bmp3); } 图像变换矩阵初始矩阵 123[1 0 0 0 1 0 0 0 1] 图像处理基本变换 Translate 1234567p(x0,y0)->p(x,y)x=Δx+x0y=Δy+y0变换矩阵[1 0 Δx 0 1 Δy 0 0 1 ] Rotate 以坐标原点为中心旋转 Scale 每个点的坐标等比例缩放 Skew 保持所有点的x或y轴坐标不变,对应的y或x坐标等比例平移,有水平错切和垂直错切 123456matrix.setRotate()matrix.setTranslate()matrix.setScale()matrix.setSkew()set()会重置pre().post()前乘后乘对矩阵混合使用 drawBitmapMesh()像素块 12改变图像坐标值重新定位每一个图像块drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,@NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,@Nullable Paint paint) 旗帜FlagView 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 public class FlagView extends View { private float[] orig, verts; int HEIGHT = 100, WIDTH = 100; //振幅 private float A = 20; private Bitmap bitmap; private float k; public FlagView(Context context) { this(context, null); } public FlagView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public FlagView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.car3); int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); int index = 0; orig = new float[bitmapHeight * bitmapWidth]; verts = new float[bitmapHeight * bitmapWidth]; for (int y = 0; y <= HEIGHT; y++) { float fy = bitmapHeight * y / HEIGHT; for (int x = 0; x <= WIDTH; x++) { float fx = bitmapWidth * x / WIDTH; orig[index * 2 + 0] = verts[index * 2 + 0] = fx; //+100 避免被遮挡 orig[index * 2 + 1] = verts[index * 2 + 1] = fy + 100; index += 1; } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); flagWave(); k += 0.1f; canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts, 0, null, 0, null); invalidate(); } private void flagWave() { for (int j = 0; j <= HEIGHT; j++) { for (int i = 0; i <= WIDTH; i++) { verts[(j * (WIDTH + 1) + i) * 2 + 0] += 0; //图像动起来,纵坐标周期性变化 float offsetY = (float) Math.sin((float) i / WIDTH * 2 * Math.PI + Math.PI * k); verts[(j * (WIDTH + 1) + i) * 2 + 1] = orig[(j * WIDTH + i) * 2 + 1] + offsetY * A; } } }}]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Android Notes (ListView Scroller)]]></title>
<url>%2F2018%2F03%2F02%2FAndroid-Notes-ListView-Scroller%2F</url>
<content type="text"><![CDATA[ListView使用技巧 优化技巧 ViewHolder-ListView 视图缓存机制 设置分割线divider=”@null”透明 取消点击效果,listSelector smoothScrollBy/ToPosition() 处理空ListView,listView.setEmptyView(ImageView) 滑动监听,onTouchListener,onScrollListener,GestureDetector手势识别,VelocityTracker滑动速度检测 拓展 滚动到顶部,底部时有个弹性距离可以继续滑动,松开后回弹 123456789private void init(){ mMaxOverDistance= (int) (getResources().getDisplayMetrics().density*mMaxOverDistance);}@Overrideprotected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { Log.e("overscroll","by "+mMaxOverDistance); return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxOverDistance, isTouchEvent);} 聊天ListView getItemViewType(position)-> mData.get(position).getType() getViewTypeCount()->2 getView()区分type,实例化不同布局添加内容 listview选中未选中状态: 在getView()中判断,点击选中的item的position和当前position是否相同,添加不同的布局 Android Scroll滑动效果是触摸事件不断改变View的坐标,移动位置,来达到滑动效果 触摸事件类型 12345678910111213141516171819202122232425 //单点触摸按下 public static final int ACTION_DOWN= 0; //单点触摸离开 public static final int ACTION_UP= 1; //触摸点移动 public static final int ACTION_MOVE= 2; //触摸动作取消 public static final int ACTION_CANCEL= 3; //触摸动作超出边界 public static final int ACTION_OUTSIDE= 4; //多点触摸按下 public static final int ACTION_POINTER_DOWN= 5; //多点触摸离开 public static final int ACTION_POINTER_UP= 6; int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_MOVE: break;} 实项滑动的七种方法 layout() 123456789101112131415161718192021222324252627282930313233343536相对于视图坐标系的获取坐标getX,getY,而lastX,lastY不需要重新设置坐标,因为相对于视图坐标,点击屏幕获取到的坐标会重新设置对于Android屏幕坐标系,getRawX,getRawY,lastRawX,lastRawY需要重新设置,获取准确的偏移量,第一次坐标位置getRawX,getRawY在(10,10),在移动到(20,20),偏移量为10,此时不重新设置,下个点是(50,50),偏移量是50-10=40,而实际正确的偏移量为50-20=30@Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); int rx = (int) event.getRawX(); int ry = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = x; lastY = y; lastrX = rx; lastrY = ry; break; case MotionEvent.ACTION_MOVE: int dx = x - lastX; int dy = y - lastY; int rdx = rx - lastrX; int rdy = ry - lastrY; Log.e("drag", "x=" + x + " y=" + y + " lastX=" + lastX + " lastY=" + lastY); Log.e("drag", "rx=" + rx + " ry=" + ry + " lastrX=" + lastrX + " lastrY=" + lastrY); Log.e("drag", "dx=" + dx + " dy=" + dy + " rdx=" + rdx + " rdy=" + rdy); layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);// lastrX = x;// lastrY = y; break; case MotionEvent.ACTION_UP: break; } return true; } offsetLeftAndrRight(),offsetTopAndBottom() 12offsetLeftAndRight(dx);offsetTopAndBottom(dy); LayoutParams(),ViewGroup.MarginLayoutParams 123456789FrameLayout.LayoutParams layoutParams= (FrameLayout.LayoutParams) getLayoutParams();layoutParams.leftMargin=getLeft()+dx;layoutParams.topMargin=getTop()+dy;setLayoutParams(layoutParams);ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();layoutParams.leftMargin = getLeft() + dx;layoutParams.topMargin = getTop() + dy;setLayoutParams(layoutParams); ScrollTo(x,y)/ScrollBy(dx,dy) 1234567891011121314151617181920212223 ViewGroup 移动的是子View View移动的是内容 //移动的是屏幕下方画布,会造成内容向相反方向移动 //scrollBy(dx,dy); //所以移动父视图,并且值为负,相反方向scrollby才有正确的效果case MotionEvent.ACTION_DOWN: lastX = x; lastY = y; if (!isScroll) { lastrX = rx; lastrY = ry; } break;case MotionEvent.ACTION_MOVE: int dx = x - lastX; int dy = y - lastY; //移动的是屏幕下方画布,会造成内容向相反方向移动 // scrollBy(dx,dy); //((View) getParent()).scrollBy(-dx, -dy); ((View) getParent()).scrollTo(-(int) event.getRawX() + lastrX, -(int) event.getRawY() + lastrY); isScroll = true; break; Scroller 实现平移滑动 初始化Scroller 1mScroller=new Scroller(context); 重写computeScroll()模拟滑动 1234567891011 @Override public void computeScroll() { super.computeScroll(); //判断Scroll是否执行完毕,true 没有执行完 if(mScroller.computeScrollOffset()){ ((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); //只能通过invalidate()—>draw()->computeScroll()间接调用computeScroll(), //结束后computeScrollOffset()返回false,中断循环 postInvalidate(); }} startScroll 开始模拟滑动 123456789101112131415161718192021222324252627@Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); int rx = (int) event.getRawX(); int ry = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = x; lastY = y; lastRx = rx; lastRy = ry; break; case MotionEvent.ACTION_MOVE: ((View) getParent()).scrollTo(-rx + lastRx, -ry + lastRy); break; case MotionEvent.ACTION_UP: View parent = (View) getParent(); //模拟滑动,起始坐标和偏移量,滑动偏移为滑动距离的负数,相反方向滑动 mScroller.startScroll(parent.getScrollX(), parent.getScrollY(), -parent.getScrollX(), -parent.getScrollY()); //通知重绘 invalidate(); break; } return true; } ViewDragHelper实项Drawlayout菜单侧滑 youtubelayout 回调 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { /**重写tryCaptureView,何时开始检测触摸事件, * 当前触摸的是MainView时开始检测*/ @Override public boolean tryCaptureView(View child, int pointerId) { return mMainView == child; } /**水平垂直方向上的滑动,默认返回0,不滑动*/ @Override public int clampViewPositionHorizontal(View child, int left, int dx) {// return super.clampViewPositionHorizontal(child, left, dx); return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) {// return super.clampViewPositionVertical(child, top, dy); return top; } /**拖动结束后调用,自动滑动打开或关闭菜单*/ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); if (mMainView.getTop() > mHeight / 2) { mViewDragHelper.smoothSlideViewTo(mMainView, 0, (int) (mHeight * 1.2f)); } else { if (mMainView.getLeft() < mWidth / 2) { //关闭菜单 mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0); } else { mViewDragHelper.smoothSlideViewTo(mMainView, (int) (mWidth * 1.2f), 0); } } ViewCompat.postInvalidateOnAnimation(ViewDragHelperView.this); } /**更改scale进行缩放*/ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { super.onViewPositionChanged(changedView, left, top, dx, dy); } /**状态改变*/ @Override public void onViewDragStateChanged(int state) { super.onViewDragStateChanged(state); } /**触摸后回调*/ @Override public void onViewCaptured(View capturedChild, int activePointerId) { super.onViewCaptured(capturedChild, activePointerId); } }; 获取宽度 123456@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = mMenuView.getMeasuredWidth(); mHeight = mMenuView.getMeasuredHeight(); } 获取MenuView,MainView 123456@Overrideprotected void onFinishInflate() { super.onFinishInflate(); mMenuView = getChildAt(0); mMainView = getChildAt(1);} 触摸事件拦截,并传递给VieDragHelper 12345678910111213141516/** * 触摸事件传递给ViewDragHelper,必须写 */@Overridepublic boolean onTouchEvent(MotionEvent event) { mViewDragHelper.processTouchEvent(event); return true;}/** * 重写事件拦截方法,把事件传递给ViewDragHelper */@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { return mViewDragHelper.shouldInterceptTouchEvent(ev);} 重写computeScroll() 12345678910/** * 平滑移动 */@Overridepublic void computeScroll() { super.computeScroll(); if (mViewDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); }}]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[Docker 编译Android源码]]></title>
<url>%2F2018%2F02%2F26%2FDocker-%E7%BC%96%E8%AF%91Android%E6%BA%90%E7%A0%81%2F</url>
<content type="text"><![CDATA[Windows 安装Docker Cannot install packages inside docker Ubuntu image 12345678910It is because there is no package cache in the image, you need to run:apt-get -qq updatebefore installing packages, and if your command is in a Dockerfile, you'll then need:apt-get -qq -y install curlAlways combine RUN apt-get update with apt-get install in the same RUN statement, for exampleRUN apt-get update && apt-get install -y package-bar 参考使用Docker编译Android系统源码 Ubuntu14.10 编译 Android5.0 源码 更换系统镜像源 apt-get install wget wget 网址而要让档案自动储存到指令的目录下,则需要借用-P这个参数,可以使用以下的指令 wget -P 目录 网址举例来说,如果你要放到/root底下,你可以打下列的指令:wget -P /root 网址 安装配置openjdk-7-jreubuntu 16 无法安装jdk7解决办法 12345678910111213sudo add-apt-repository ppa:openjdk-r/ppa sudo apt-get update sudo apt-get install openjdk-7-jdkvim/gedit ~/.bashrcexport JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64export JRE_HOME=$JAVA_HOME/jreexport CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATHexport PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/bin/tools.jar:$JRE_HOME/binexport ANDROID_JAVA_HOME=$JAVA_HOMEexport USE_CCACHE=1source ~/.bashrc 安装其他工具 12345678910111213141516dpkg --print-architecture # 若支持,输出 amd64dpkg --print-foreign-architectures # 若支持,输出 i386//手动开启支持dpkg --add-architecture i386apt-get updatesudo apt-get install gcc-multilib g++-multilib build-essentialsudo apt-get install git-core gnupg bison flex gperf pngcrush bc zip curl lzopsudo apt-get install schedtool libxml2 libxml2-utils xsltproc squashfs-toolssudo apt-get install libesd0-dev libsdl1.2-dev libwxgtk2.8-dev libswitch-perlsudo apt-get install libssl1.0.0 libssl-dev lib32readline-gplv2-dev libncurses5-devcd /AOSPexport USE_CCACHE=1export CCACHE_DIR=/<path_of_your_choice>/.ccacheprebuilts/misc/linux-x86/ccache/ccache -M 50G windows 上创建/androidsource git clone https://aosp.tuna.tsinghua.edu.cn/platform/manifest.git输入命令,切换到manifest目录 cd manifest Git tag 列出android各个分支版本下载android-android-5.1.1_r9git checkout android-5.1.1_r9 运行 python download-src.py 1234567891011121314151617181920212223242526272829import xml.dom.minidom import os from subprocess import call #downloaded source path rootdir = "E:/androidsource" #git program path git = "D:/Git/bin/git.exe"dom = xml.dom.minidom.parse("E:/androidsource/manifest/default.xml") root = dom.documentElementprefix = git + " clone https://aosp.tuna.tsinghua.edu.cn/" suffix = ".git" if not os.path.exists(rootdir): os.mkdir(rootdir) for node in root.getElementsByTagName("project"): os.chdir(rootdir) d = node.getAttribute("path") last = d.rfind("/") if last != -1: d = rootdir + "/" + d[:last] if not os.path.exists(d): os.makedirs(d) os.chdir(d) cmd = prefix + node.getAttribute("name") + suffix call(cmd) 出现 curl: (22) The requested URL returned error: 404 Not Found Server does not provide clone.bundle; ignoring. 怎么办?无视即可 Linux下载repo 123456789101112131415161718192021mkdir ~/binPATH=~/bin:$PATHcurl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o repo(或者下面的地址)curl https://storage.googleapis.com/git-repo-downloads/repo > repochmod a+x reporepo的运行过程中会尝试访问官方的git源更新自己,如果想可以使用[tuna的镜像](https://mirrors.tuna.tsinghua.edu.cn/help/git-repo/)源进行更新,可以将如下内容复制到你的~/.bashrc里export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'并重启终端模拟器cd /AOSP初始化同步reporepo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-5.1.1_r9repo sync -f -j4AOSP 未下完成是不显示文件,.repo是隐藏文件夹,ctrl+h显示有问题时删除缓存文件rm -rf * -R 设置repo环境变量 123vim ~/.bashrcexport PATH=~/bin:$PATHsource ~/.bashrc Ubuntu 下载源码 start a shell session in a running container 12345docker exec -it 98e0b4f60ec3 bash$ sudo docker attach 665b4a1e17b6 #by ID or$ sudo docker attach loving_heisenberg #by Name$ root@665b4a1e17b6:/# Windows mount shared folder to Ubuntu 12345678910111213141516171819202122In Window Host:-1. Create a folder "aosp" -- Window2. Right Click on "nandan" -- Properties -- sharing -- Advance Sharing3. Check option "share this folder" - permission -remove Group or Username "Everyone".4. Add User - select your user account from List5. Check Full Control,Read,Change6. Applu- Ok - Exit7. open cmd - ipconfig - copy ipv4 for Wireless lan Adapter incase if you are using WIFI----------------In Ubuntu VM :-1. Open terminal.. 2. become root $sudo su-3. apt-cache search cifs4. if cifs utils not present then install $ sudo apt-get install cifs-utils5. df -h6. $sudo mkdir /media/test7. $sudo mount.cifs-o username= window_username //window_ip_address/window_share_folder_name /media/test复制win共享文件夹到Ubuntucp -r aosp(win shared folder) AOSP Android系统源代码的下载与编译 初始化源码所需环境变量和参数 1source build/envsetup.sh lunch 命令选择编译目标 make -j16 (nproc 查看cpus)]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>源码</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android Notes (System ADB UI)]]></title>
<url>%2F2018%2F02%2F25%2FAndroid-Notes%2F</url>
<content type="text"><![CDATA[Android系统架构 Android系统分四层:Linux内核层,库和运行时,Framework层和应用层。 Linux包括硬件驱动,进程管理,安全系统 Dalvik虚拟机,运行时编译;ART虚拟机安装时编译 Framework ActivityManager BackupManager Bluetooth ContentProviders LocationManger Map libraries MediaPlayer NotificationManager PackageManager ResourceManager SearchManager SharedPreference TelephoneManager WidgetProvider WindowManager ViewSystem XMPPService 标准库 Apache HTTP OpenGL ES Open SSL SQLite Webkit ICU Android NDK/SDK APP(解压后包含内容中有) AndroidManifeset Dalvik Classes Resource bundle NDK APP中包含JNI lib App组件 Activity BroardCastReceiver ContentProvider Service Context Application Context(应用启动时创建,整个生命周期) Activity Context Service Context Android 源代码目录与系统目录 Android源代码目录 Makefile (自动化编译,定制规则) bionic (bionic C库) bootable (启动引导相关代码) build (系统编译规则等基础开发包配置) cts (Google兼容性测试标准) dalvik (dalvik虚拟机) development (应用程序开发) external (开源模块) frameworks (框架) hardware (硬件适配层HAL代码) out (编译完成后代码输出目录) packages (应用程序包) prebuilt (x86 arm 架构下预编译资源) sdk system (底层文件系统库,应用及组件) vendor (厂商定制代码) Android 系统目录 (ADB ls命令查看系统目录) /system/app/ 系统app /system/bin/ Linux自带组件 /system/build.prop/ 系统属性信息 /system/fonts/ 字体 /system/framework/ 核心文件,框架层 /system/lib/ .so文件 /system/media/ 音频,闹钟,短信,来电等铃声 /system/usr/ 用户配置文件,键盘布局,共享,时区文件 /data/app/ 安装或升级的app /data/data/ App数据文件,数据库等信息 /data/system/ 系统信息 /data/misc/ WIFI,蓝牙,签名,VPN等信息 Android 开发工具 SDK镜像地址 android-studio Genymotion 安装arm架构Genymotion-ARM-Translation Android Debug ADB Command (SDK platform-tools) adb shell ls|grep “data” / cd data android list target 显示系统Android平台 id:1 or android-19 adb install -r a.apk 重新安装保留数据,安装位置/data/local/tmp/a.apk adb push a.apk /system/app 文件写入存储系统,有权限时可以安装系统应用 adb push /system/temp/ c:\Desktop 从手机获取文件 adb shell -> logcat|grep “abc” 查看log adb remount(重新挂载系统分区,使系统分区重新可写) -> adb shell -> cd system/app -> rm a.apk adb shell df 查看系统盘符 adb shell pm list packages -f 输出所有安装应用 adb shell input keyevent 3 模拟按键输入 keycode adb shell input touchscreen swipe 18 655 18 500 模拟滑动输入 adb shell dumpsys activity activities|grep “tencent” 列出activity 运行状态,过滤”tencent” pm list packages -f 列出所有package adb shell am start -n com.qiyi.video/org.qiyi.android.video.MainActivity 启动一个Activity adb shell screenrecord /sdcard/a.mp4 录制屏幕 adb reboot 重启 adb 命令来源 /frameworks/base/cmds/ /system/core/toolbox/ Android 自定义控件 控件架构 ContentView id为content的FrameLayout, onCreate()方法中调用setContentView()后ActivityManagerService会回调onResume(),此时系统才会把DecorView添加到PhoneWindow中,显示并完成绘制 View的测量 onMeasure() MeasureSpec 32位int值,高2位为测量的模式,低30位为测量的大小 EXACTLY 精确值模式,具体数值 AT_MOST 最大值模式,尺寸不超过父控件允许的最大尺寸 UNSPECIFIED 不指定,自定义view时使用,如果View要支持wrap_content,重写onMeasure()指定wrap_content时的大小 1234protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } 123456789101112131415161718/** * 宽高值自定义,在wrap_content时如果没有指定大小就会默认填充整个父布局 */private int measureWidth(int measureSpec){ int result=0; int specMode=MeasureSpec.getMoode(measureSpec); int specSize=MeasureSpec.getSize(measureSpec); if(specMode=MeasureSpec.EXACTLY){ result=specSize; }else{ result=200; if(specMode=MeasureSpec.AT_MOST){ result=Math.min(result,specSize) } } return result;} View的绘制 重写onDraw(Canvas canvas) Canvas 画板1234567canvas.drawBitmap(bitmap1,0,0,null);canvas.drawBitmap(bitmap2,0,0,null);装载画布Canvas mCanvas=new Canvas(bitmap2);mCanvas.drawXXX刷新view的时候,bitmap2发生改变,没有把图形直接绘制在canvas,而是通过改变bitmap2绘制复杂图形,绘制多个拆分的小图形单元 ViewGroup的测量在wrap_content时遍历子View的大小,决定自己的大小,其他模式下通过具体的指定值设置大小。遍历子View,调用子View的Measure方法获得每一个子View 的测量结果,然后遍历子View进行布局Layout,重写onLayout()控制子View显示位置的逻辑,如果支持wrap_content需要重写onMeasure() ViewGroup的绘制 绘制背景颜色以及遍历子View的绘制 自定义View比较重要的回调方法 onFinishInflate() onSizeChanged() onMeasure() onLayout() onDraw() onTouchEvent() 对原生控件扩展 改变绘制顺序,需要改变画布位置状态时,先保存状态,等改变后,再恢复 12345....canvas.save();canvas.translate(10,0);super.onDraw(canvas);canvas.restore(); LinearGradient,Matrix实项文字闪动效果,onSizeChanged()中初始化,根据宽度设置LinearGradient渐变渲染器 1234567891011121314151617181920212223242526protected void onSizeChanged(int w,int h,int oldw,int oldh){ super.onSizeChanged(w,h,oldw,oldh); if(mViewWidth==0){ mViewWidth=getMeasureWidth(); if(mViewWidth>0){ //获取Paint,设置LinearGradient mPaint=getPaint(); mLinearGradient=new LinearGradient(0,0,mViewWidth,0,new int[] {Color.BLUE,Color.RED},null,Shader.TitleMode.CLAMP); //设置着色器 mPaint.setShader(mLinearGradient) //平移变换矩阵 mGradientMatrix=new Matrix(); } } }public void onDraw(Canvas canvas){ if(mGradientMatrix!=null){ mTranslate+=mViewWidth/5; if(mTranslate>2*mViewWidth){ mTranslate=-mViewWidth; } mGradientMatrix.setTranslate(mTranslate,0); mLinearGradient.setLocalMatrix(mGradientMatrix); postInvalidateDelayed(100); }} 复合控件继承ViewGroup 定义属性 res/value/attrs.xml 123456789101112<declare-styleable name="V"> <attr name="title|size|background" format="string|dimension| reference|color"/></declare-styleable>//获取TypedArray,存储了属性值TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.V);ta.getColor();ta.getString();ta.getDrawable();ta.getDimension();//资源回收ta.recycle(); 组合控件 12345678mBtn1=new Button();mBtn2=new Button();mTextView=new TextView();mParams=new LayoutParams();mParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,true);addView(mBtn1,mParams);addView(mBtn2,mParams);addView(mTextView,mParams); 定义接口,添加事件 public method include layout 重写View 比例图 中间圆形加中心文字,外圈一定比例的圆弧 length 正方形边长 1234567891011- 圆 mCircle=length/2;mRadius=length/4; drawCirle()- 圆弧//所处的矩形框mRect=new Rect(0.1*length,0.1*length,0.9*length,0.9*length);drawArc(mRect,270,mSweepAngle,false,mArcPaint);- 文字drawText();setSweepValue(100); 音频条形图 onSizeChanged()设置渐变色 onDraw() 绘制多个小矩形 postInvalidateDelayed(300) 123456789101112131415161718192021222324252627282930@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = getWidth(); Log.e("Audio", "mWidth=" + mWidth + " w=" + w + " h=" + h + " oldw=" + oldw + " oldh=" + oldh); mRectHeight = getHeight(); mRectWidth = (int) (mWidth * 0.6 / mCount); mLinearGradient = new LinearGradient(0, 0, mRectWidth, mRectHeight, Color.YELLOW, Color.RED, Shader.TileMode.CLAMP); mMatrix = new Matrix(); mPaint.setShader(mLinearGradient);}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); mTranslate += mRectWidth / 5; if (mTranslate > 2 * mRectWidth) { mTranslate = -mRectWidth; } mMatrix.setTranslate(mTranslate, 0); mLinearGradient.setLocalMatrix(mMatrix); for (int i = 0; i < mCount; i++) { mCurrentHeight = (float) (mRectHeight * Math.random()); canvas.drawRect((float) (mWidth * 0.2 + offset + mRectWidth * i), mCurrentHeight, (float) (mWidth * 0.2 + mRectWidth * (i + 1)), mRectHeight, mPaint); } postInvalidateDelayed(300);} 自定义ViewGroup 重写onMeasure() onLayout() onTouchEvent() 自定义ScrollView,滑动后回位到item的起始位置 遍历的方式通知子View对自身测量 1234567891011121314private void init() { mScroller = new OverScroller(getContext()); mScreenHeight = getResources().getDisplayMetrics().heightPixels; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count=getChildCount(); for (int i = 0; i < count; i++) { View child=getChildAt(i); measureChild(child,widthMeasureSpec,heightMeasureSpec); } } 对子View进行位置放置 123456789101112131415@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int screenHeight = getResources().getDisplayMetrics().heightPixels; //获取整个ViewGroup高度 MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams(); layoutParams.height = childCount * screenHeight; setLayoutParams(layoutParams); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() != View.GONE) { child.layout(l, screenHeight * i, r, screenHeight * (i + 1)); } }} 在onTouchEvent()中添加滑动事件,使用scrollBy() 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = event.getY(); //起点 mStart = getScrollY(); break; case MotionEvent.ACTION_MOVE: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } float dy = mLastY - event.getY(); Log.d("Scroll", "======" ); Log.d("Scroll", "dy = " + dy); Log.d("Scroll", "getScrollY() = " + getScrollY()); Log.d("Scroll", "getHeight() = " + getHeight()); Log.d("Scroll", "mScreenHeight() = " + mScreenHeight); Log.d("Scroll", "getHeight() - mScreenHeight = " + (getHeight() - mScreenHeight)); Log.d("Scroll", "mLastY = " + mLastY); Log.d("Scroll", "mStart = " + mStart); if (getScrollY() < 0) { //最顶端,超过0时,不再下拉 //不设置这个,getScrollY一直是负数 dy = 0; } if (getScrollY() > getHeight() - mScreenHeight) { //滑到最底端时,不再滑动 //不设置这个,getScrollY一直是大于getHeight() - mScreenHeight的数 dy = 0; } scrollBy(0, (int) dy); //不断的设置Y,在滑动的时候子view就会比较顺畅 mLastY = event.getY(); break; case MotionEvent.ACTION_UP: mEnd = getScrollY(); int dScrollY = mEnd - mStart; //向上,向下滑动,超过1/3屏幕高度, //结束滑动时滚动到下个位置,没有超过时复位 if (dScrollY > 0) { if (dScrollY < mScreenHeight / 3) { mScroller.startScroll(0, mEnd, 0, -dScrollY, 200); } else { mScroller.startScroll(0, mEnd, 0, mScreenHeight - dScrollY, 200); } } else { if (-dScrollY < mScreenHeight / 3) { mScroller.startScroll(0, mEnd, 0, -dScrollY, 200); } else { mScroller.startScroll(0, mEnd, 0, -(mScreenHeight + dScrollY), 200); } } break; } // 重绘执行computeScroll() postInvalidate(); //需要返回true否则down后无法执行move和up操作 return true;}/** * Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性, * 但它并不是UI,也不是滑动辅助UI运动,反而是单纯地为滑动提供计算 * 需要invalidate()之后才会调用,这个方法在onDraw()中调用 */@Overridepublic void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { scrollTo(0, mScroller.getCurrY()); postInvalidate(); }} 事件拦截机制,涉及触摸事件MotionEvent(),onTouchEvent(),dispatchTouchEvent(),onInterceptTouchEvent(),多个View,ViewGroup之间对事件的处理 ViewGroup A,B 1234567891011121314151617@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { Log.e("A","dispatchTouchEvent "+ev.getAction()); return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { Log.e("A","onInterceptTouchEvent "+ev.getAction()); return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent ev) { Log.e("A","onTouchEvent "+ev.getAction()); return super.onTouchEvent(ev);} View 1234567891011@Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e("MyView","dispatchTouchEvent "+ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { Log.e("MyView","onTouchEvent "+ev.getAction()); return super.onTouchEvent(ev); } View位置顺序是A最外层,B中间,MyView最底层。传递分发机制是A->B->MyView,处理机制时MyView->B->A 123事件传递返回值:True 拦截,不继续传递;False,不拦截,继续传递事件处理返回值:True 处理了不用传递回上级,False 给上级处理初始情况都是False]]></content>
<categories>
<category>Android</category>
</categories>
</entry>
<entry>
<title><![CDATA[sh 简单程序]]></title>
<url>%2F2018%2F02%2F24%2Fsh-%E7%AE%80%E5%8D%95%E7%A8%8B%E5%BA%8F%2F</url>
<content type="text"><![CDATA[sh-demo 直接执行命令123datewho 创建变量,如果是字符串的话,有空格的一定要用双引号,否则会被解析成命令123NDK=10text="i love you" 命令的执行结果作为变量的值,例如以当前时间作为文件名123text1=datetext2=$(who) 输出1234567echo $NDKecho $textecho $text1echo $text2 字符串拼接,同理:有空格需要用双引号1echo "$text very much" 如果需要输出$的话,需要使用转义字符1echo "$" 运行两种方式 1 ./bash.sh 2 sh bash.sh 如果sh command not found 或者 sh cannot open file 程序中的/r/n 换行符限制 命令退出的状态 命令执行退出的状态: 0 成功 127 没有找到命令 1 未知错误 126 命令不可执行 查看与退出状态指定: 查看上一次命令的执行状态echo $?在shell脚本中,自己指定退出的状态exit 状态码 grep命令是查找命令,例如查找test文本在test.txt中所在的行数:grep -n test test.txt 可以结合test命令,如果条件成立,test命令以状态为0退出,if条件成立。 test命令简单形式,用中括号,注意空格要加上 a -gt b 要加上空格,程序才能运行 比较大小: #!/bin/bash a=10b=5 test命令简单形式 if [ a -gt b ] then echo "a greater than b" else echo "a smaller than b" fi test数值比较: -gt 大于 -eq 等于 -le 小于 -ne 不等于 判空: #!/bin/bash str1=””if [ str1 = “” ] then echo "有内容"else echo "没内容"fi test字符串比较: str1 == str2 str1 != str2 str1 < str2 -n str1 长度是否非0 -z str1 长度是否为0 检查目录是否存在: #!/bin/bash mydir=/usr/jason -d检查目录是否存在 if [ -d $mydir ] then echo "$mydir exist" cd $mydir ls else echo "mydir not exist" fi test文件比较: -d 检查是否存在,并且是一个目录 -e 检查file是否存在 -f 检查是否存在,并且是一个文件 -r 检查是否存在,并且可读,余此类推:-w、-x file1 -nt file2 file1比file2新 file1 -ot file2 file1比file2旧 case语句 基本格式是: case命令case 变量 inpattern1) 命令;;pattern2) 命令;;*) 默认命令;;esac 例子: #!/bin/bash testuser=rose case $testuser in rose) echo "hi,$testuser";; ricky) echo "hello, ricky";; *) echo "defaults";; esac While循环 基本格式: while test command(或者[])do 命令done 例子: #!/bin/bash a=10 while [ $a -gt 0 ] do echo "num:$a" 赋值不用使用$符号 a=[ a - 1 ]done]]></content>
<categories>
<category>Linux</category>
</categories>
</entry>
<entry>
<title><![CDATA[Linux 常用快捷键 命令 设置等操作]]></title>
<url>%2F2018%2F02%2F24%2FLinux-%E5%B8%B8%E7%94%A8%E5%BF%AB%E6%8D%B7%E9%94%AE%2F</url>
<content type="text"><![CDATA[常用快捷键 Ctrl+Alt+T 打开终端 Ctrl+L 清空屏幕(功能相当于命令clear) Ctrl+U 剪切文本直到行的起始(可以用于清空行) Ctrl+K 剪切文本直到行的末尾 Ctrl+Y 粘贴最近剪切的文本 Ctrl+C 杀死当前进程(也可以用来清空当前行) Ctrl+D 退出当前Shell(功能相当于命令exit) 或者 删除当前的字符 Ctrl+A 行首 Ctrl+E 行尾 Home/End 行首/行尾 Ctrl+F 向前移动一个字符 Ctrl+B 向后移动一个字符 Ctrl+P 或 Ctrl+N 上下历史记录 上下方向键 上下历史记录 Ctrl+Shift+C 复制 Ctrl+Shift+V 粘贴 还有Tab补全,按住Ctrl键进行块选择. 鼠标中键:粘贴(在gnome-terminal中使用”菜单键+P”也是可以粘贴的) 设置代理1.通过export http代理使用apt-get(临时有效)在使用apt-get之前,在终端中输入以下命令 12export http_proxy=""export https_proxy="" 2.apt.conf文件中配置http代理信息(永久有效) sudo gedit /etc/apt/apt.conf在您的apt.conf文件中加入下面这行1Acquire::http::Proxy "http://proxy_addr:proxy_port"; 3..bashrc文件中配置代理信息(apt-get, wget 等等)(全局有效) gedit ~/.bashrc 在.bashrc文件末尾添加如下内容1export http_proxy="http://proxy_addr:proxy_port" vi操作 跳到文本的最后一行:按“G”,即“shift+g” 跳到最后一行的最后一个字符 : 先重复1的操作即按“G”,之后按“$”键,即“shift+4”。 跳到第一行的第一个字符:先按两次“g”, 跳转到当前行的第一个字符:在当前行按“0”。 vi加密。进入vi,输入”:” + “X” 之后就提示你输入两次密码。之后:wq 保存退出。再次进入时就提示你输入密码了。如果你不想要密码了,就:X 提示你输入密码时连续按两次回车 编辑只读文件存在历史,buffer状态等,不能直接修改保存文件,解决方案1:w !sudo tee % 设置环境变量12345678910111213141516vim .bashrc echo $PATH (列出所有环境变量) echo $JAVA_HOME (列出某个环境变量值) source .bashrc (保存并且生效) NDKROOT='/home/willkernel/Downloads/android-ndk-r15c'export NDKROOTexport PATH=$NDKROOT:$PATHANDROID='/home/willkernel/Downloads/android-studio/bin'export ANDROIDexport PATH=$ANDROID:$PATHFFMPEG='/home/willkernel/Downloads/ffmpeg-2.6.9'export FFMPEGexport PATH=$FFMPEG:$PATH 常用命令50 Most Frequently Used UNIX / Linux Commands (With Examples)50个最常用的Unix/Linux命令 其他 install lantern in Ubuntu and launch lantern 1sudo dpkg -i lantern *.deb 卸载程序 123dpkg --list 已列出安装软件sudo apt-get --purge remove <programname> 删除程序和配置文件sudo apt-get remove <programname> 删除程序保留配置文件]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Skills</tag>
</tags>
</entry>
</search>