├── Android进阶
├── Github开源项目源码解析
│ ├── OkHttp完全解析.md
│ └── Volley完全解析.md
├── transformResourcesWithMergeJavaResForDebug.md
├── 开源库的基本使用
│ ├── Retrofit2.0基本使用.md
│ └── Dagger-依赖注入入门.md
├── Fragment中获取Context对象的最佳方案.md
├── Android开发艺术探索(研读笔记)
│ ├── 01-Activity的生命周期.md
│ └── 02-Activity的启动模式.md
├── Android加载图片方案-避免OOM.md
├── ListView适配器的使用技巧.md
├── Android动画
│ └── FAB动画以及RecycleView载入动画.md
├── 【译文】RxAndroid and Kotlin(Part 1).md
└── MaterialDesign使用(二).md
├── Java基础
├── JDK5.0新特性.md
├── Eclipse快捷键.md
└── 单例基础.md
├── README.md
├── 软件测试
└── 软件测试.md
├── JavaWeb
├── HTML基础.md
├── SQL基础.md
└── JavaWeb基础笔记.md
├── python
└── 基础语句.md
├── 工具
└── Emacs入门.md
└── Android基础
├── 广播与服务day2笔记.md
├── 页面跳转和数据传递笔记.md
├── 第五章详解广播机制.md
├── 旧特性笔记.md
├── AndroidProject思路汇总01.md
├── 内容提供者笔记.md
├── 广播与服务day1笔记.md
├── 网络编程day2笔记.md
├── 网络编程day1笔记.md
├── 数据存储和界面展现day1笔记.md
├── 多媒体编程笔记.md
└── 数据存储和界面展现day2笔记.md
/Android进阶/Github开源项目源码解析/OkHttp完全解析.md:
--------------------------------------------------------------------------------
1 | ---
2 | - 邮箱 :elisabethzhen@163.com
3 | - Good Luck!
--------------------------------------------------------------------------------
/Java基础/JDK5.0新特性.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimon94/AndroidNotes/HEAD/Java基础/JDK5.0新特性.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidNotes
2 |
3 | 
4 | > 宁欺白须公,
5 |
6 | > 莫欺少年穷,
7 |
8 | > 终须有日龙穿凤,
9 |
10 | > 唔信一世裤穿窿。
11 |
12 | ---
13 |
14 | ## Developed By
15 |
16 | - Dimon - [elisabethzhen@163.com](elisabethzhen@163.com)
17 |
18 | ---
--------------------------------------------------------------------------------
/软件测试/软件测试.md:
--------------------------------------------------------------------------------
1 | # 软件测试
2 | - TMM
3 | - 单元测试、自动化测试、负载压力测试、
4 |
5 | # 测试
6 | ### 按岗位划分
7 | * 黑盒测试:测试逻辑业务
8 | * 白盒测试:测试逻辑方法
9 | * 灰盒测试:
10 |
11 | ### 按测试粒度分
12 | * 方法测试:function test
13 | * 单元测试:unit test
14 | * 集成测试:integration test
15 | * 系统测试:system test
16 | * 确认测试:
17 | * 验收测试:
18 |
19 | ### 按测试暴力程度
20 | * 冒烟测试:smoke test
21 | * 压力测试:pressure test
22 |
23 | ---
24 |
25 | # 测试原则
26 |
27 | - 所有的软件测试都应追溯到用户需求
28 | - 应当把“尽早地和不断地进行软件测试”作为软件测试者的座右铭
29 | - 完全测试是不可能的,测试需要终止
30 | - 输入量太大
31 | - 输出结果太大
32 | - 路径组合太多
33 | - 测试无法显示软件潜在的缺陷
34 | - 充分注意测试中的群集现象
35 | - 程序员应避免坚持自己的程序
36 | - 尽量避免测试的随意性
37 |
38 | ---
39 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
40 | - Good Luck!
41 |
--------------------------------------------------------------------------------
/Android进阶/transformResourcesWithMergeJavaResForDebug.md:
--------------------------------------------------------------------------------
1 |
2 | #Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.
3 | >com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/services/javax.annotation.processing.Processor
4 | File1: C:\Users\*******\caches\modules-2\files-2.1\com.jakewharton\butterknife\7.0.1\d5d13ea991eab0252e3710e5df3d6a9d4b21d461\butterknife-7.0.1.jar
5 | File2: C:\Users\*******\caches\modules-2\files-2.1\io.realm\realm-android\0.86.0\e49a43bcbbc3d5e5491dd2f4d3799a504b618e6b\realm-android-0.86.0.jar
6 |
7 | ```java
8 | packagingOptions {
9 | exclude 'META-INF/services/javax.annotation.processing.Processor' // butterknife
10 | }
11 |
12 | ```
13 | ---
14 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
15 | - Good Luck!
--------------------------------------------------------------------------------
/JavaWeb/HTML基础.md:
--------------------------------------------------------------------------------
1 | # HTML基础
2 |
3 | - 内容:可能是文本,也可能是超文本的数据,例如图片、音频和视频等等,这些内容最终会在浏览器当中显示出来。
4 | - 标签:标签本身不会在浏览器当中显示出来,但是标签可以用来改变内容的显示形式,例如说文本加粗、斜体或者是图片的大小等等;
5 |
6 | - 网页的基础框架结构:
7 | - 一个网页首先需要一个标签将这个网页当中的所有内容包含进去,这个标签本身只是用于说明在其中包含的内容都是HTML代码,没有什么其他的作用。
8 | - HTML标签当中包含有另外两个标签,第一个是
标签,另外一个是标签
9 | - 在日常的开发当中head标签当中的内容通常用于对整个HTML网页进行设置,比如说网页是否能够显示中文,网页的标题是什么。而body标签当中的内容才是真正的要显示在浏览器当中供用户观看的内容
10 |
11 | - 链接就相当于互联网世界当中的货币,它把散落在世界各个角落的信息连接起来
12 | - 大家可以简单的把链接理解为互联网当中的传送门
13 | - 如果需要在页面当中插入一个链接,就需要使用链接内容标签
14 | - 除了href属性之外,链接还拥有一个名为target的属性
15 | - 第一种:target="_blank",另一种是target="_self"
16 |
17 | - 锚点:标签除了可以表示一个超级链接之外,还可以表示同一份文档当中的另一个点,此时需要使用标签当中的name属性
18 | - 使用锚点的时候,首先需要在当前的HTML网页当中标识一个点
19 | -
20 | - 在网页当中的其他地方使用标签的href属性引用这个名字,这样使用的时候需要在所应用的锚点名称前面加上#:
21 | - > 用 C-n 或 C-p 将光标移到上图的中央。
20 | 按 C-l,整幅图会被显示在屏幕的中央。
21 |
22 | “P N B F”四个字母分别代表了四个词,用这四个词记忆这些组合键会更容易:P 代表 previous(上一行),N 代表 next(下一行),B 代表 backward(回退),而 F 则代表 forward(前进)。这些组合键今后将与你形影不离
23 |
24 | - C-f 向右移动一个字符
25 | - C-b 向左移动一个字符
26 |
27 | - M-f 向右移动一个词【对中文是移动到下一个标点符号】
28 | - M-b 向左移动一个词【对中文是移动到上一个标点符号】
29 |
30 | - C-n 移动到下一行
31 | - C-p 移动到上一行
32 |
33 | - C-a 移动到行首
34 | - C-e 移动到行尾
35 |
36 | - M-a 移动到句首
37 | - M-e 移动到句尾
38 |
39 | - 删除光标前的一个字符
40 | - C-d 删除光标后的一个字符
41 |
42 | - M- 移除光标前的一个词
43 | - M-d 移除光标后的一个词
44 |
45 | - C-k 移除从光标到“行尾”间的字符
46 | - M-k 移除从光标到“句尾”间的字符
47 | - C-y 召回,类似于粘贴
48 |
49 | C-x 的扩展命令有很多,下面列出的是你已经学过的:
50 | - C-x C-f 寻找文件。
51 | - C-x C-s 保存文件。
52 | - C-x C-b 列出缓冲区。
53 | - C-x C-c 离开 Emacs。
54 | - C-x 1 关掉其它所有窗格,只保留一个。
55 | - C-x u 撤销。
56 | - C-/ 撤销
57 |
--------------------------------------------------------------------------------
/Java基础/Eclipse快捷键.md:
--------------------------------------------------------------------------------
1 | #eclipse的使用
2 | ###常用快捷键
3 | - 内容提示:`Alt` + `/`
4 | - 快速修复:`Ctrl` + `1`
5 | - 导包:`Ctrl` + `Shift` + `O`
6 | - 格式化代码:`Ctrl` + `Shift` + `F`
7 | - 向前向后:`Alt` + 方向键
8 | - 添加注释:`Ctrl` + `Shift` + `/`
9 | - 除去注释:`Ctrl` + `Shift` + `\`
10 | - 查看方法说明:`F2`
11 | - 重置透视图:`Window` + `Reset perspective`
12 | - 更改为大写:`Ctrl` + `Shift` + `X`
13 | - 更改为小写:`Ctrl` + `Shift` + `Y`
14 | - 复制行:`Ctrl` + `Alt` + 向下键(有些不能用)
15 | - 移动行:`Alt` + 向上 向下键
16 | - 查看类的继承关系 `Ctrl` + `T`
17 | - 查看源代码:
18 | - `Ctrl` + 右键点击
19 | - `Ctrl` + `Shift` + `T`
20 | - 查看所有的快捷键:`Ctrl` + `Shift` + `L`
21 |
22 | ## 常用快捷键 ##
23 |
24 | - ctrl + shift + o 导包
25 | - ctrl + shift + t 快速查找某个类
26 | - 先按ctrl + 2 ,再点L, 创建变量并命名
27 | - ctrl + o , 在当前类中,快速查找某个方法
28 | - ctrl + k, 向下查找某个字符串
29 | - ctrl + shift + k, 向上查找某个字符串
30 | - alt + 左方向键 跳转上一个页面
31 |
32 |
33 |
34 |
35 | ###程序的调试与运行
36 | - `F5`(跳入) `F6`(跳过) `F7`(跳出)
37 | - drop to frame : 跳到当前方法的第一行
38 |
39 | resume :跳到下一个断点(如果没有下一个,则运行完整个程序)
40 | watch:观察变量或表达式的值
41 |
42 | 断点注意的问题:
43 | 1.断点调试完成后,要在breakpoints视图中清除所有断点
44 | 2.断点调试完成后,一定要记得结束运行断点的jvm
45 |
46 | ###JUnit
47 | @Test
48 | @Before
49 | @After
50 | @BeforeClass
51 | @AfterClass
52 | Assert.assertEquals("2","1");
53 |
54 | 在Outline窗口方法上右键Run As /JUnit Test 测试某一个方法,类上右键run as /JUnit Test 测试这个类的所有方法
55 | 1.用junit进行单元测试的时候,在每个被测试的方法中必须加上@Test注解
56 | 2.用@Before注解是在每个被测试的方法前执行。
57 | 3.用@After注解是在每个被测试的方法后执行。
58 | 4.用@BeforeClass 注解的静态方法是在所有方法被测试之前执行的方法,就像类里面的构造方法一样。用来初始化一些要用到的变量等资源。
59 | 5.用@AterClass注解的静态方法是在所有被测试的方法之后执行。相当于c++中析构函数。用来释放一些资源。
--------------------------------------------------------------------------------
/JavaWeb/SQL基础.md:
--------------------------------------------------------------------------------
1 | # SQL
2 | - 数据操作语言 (DML)
3 | - 数据定义语言 (DDL)
4 |
5 | ### DML
6 | - SELECT - 从数据库表中获取数据
7 | - UPDATE - 更新数据库表中的数据
8 | - DELETE - 从数据库表中删除数据
9 | - INSERT INTO - 向数据库表中插入数据
10 |
11 | #### SQL SELECT 语句
12 | - SELECT 列名称 FROM 表名称
13 | - SELECT * FROM 表名称
14 | - 例子:SELECT LastName,FirstName FROM Persons
15 | - 星号(*)是选取所有列的快捷方式。
16 |
17 | #### SQL SELECT DISTINCT 语句
18 | - SELECT DISTINCT 列名称 FROM 表名称
19 | - 关键词 DISTINCT 用于返回唯一不同的值。
20 |
21 | #### WHERE 子句
22 | - 如需有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句。
23 | - SELECT 列名称 FROM 表名称 WHERE 列 运算符 值
24 | - SQL 使用单引号来环绕文本值(大部分数据库系统也接受双引号)。如果是数值,请不要使用引号。
25 |
26 | #### AND 和 OR 运算符用于基于一个以上的条件对记录进行过滤。
27 | - 如果第一个条件和第二个条件都成立,则 AND 运算符显示一条记录。
28 | - SELECT * FROM Persons WHERE FirstName='Thomas' AND LastName='Carter'
29 | - 如果第一个条件和第二个条件中只要有一个成立,则 OR 运算符显示一条记录。
30 | - SELECT * FROM Persons WHERE firstname='Thomas' OR lastname='Carter'
31 | - SELECT * FROM Persons WHERE (FirstName='Thomas' OR FirstName='William')
32 | AND LastName='Carter'
33 |
34 | #### ORDER BY 语句
35 | - ORDER BY 语句用于根据指定的列对结果集进行排序。
36 | - ORDER BY 语句默认按照升序对记录进行排序。
37 | - 如果您希望按照降序对记录进行排序,可以使用 DESC 关键字。
38 |
39 | #### INSERT INTO 语句
40 | - INSERT INTO 表名称 VALUES (值1, 值2,....)
41 | - 插入新的行
42 | - INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')
43 | - 在指定的列中插入数据
44 | - INSERT INTO Persons (LastName, Address) VALUES ('Wilson', 'Champs-Elysees')
45 |
46 | #### Update 语句
47 | - UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
48 |
49 | #### DELETE 语句
50 | - DELETE 语句用于删除表中的行。
51 | - DELETE FROM 表名称 WHERE 列名称 = 值
52 | - 删除所有行: DELETE FROM table_name
53 |
54 | ### DDL
55 | - CREATE DATABASE - 创建新数据库
56 | - ALTER DATABASE - 修改数据库
57 | - CREATE TABLE - 创建新表
58 | - ALTER TABLE - 变更(改变)数据库表
59 | - DROP TABLE - 删除表
60 | - CREATE INDEX - 创建索引(搜索键)
61 | - DROP INDEX - 删除索引
62 |
63 | ---
64 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
65 | - Good Luck!
--------------------------------------------------------------------------------
/Android进阶/开源库的基本使用/Retrofit2.0基本使用.md:
--------------------------------------------------------------------------------
1 | # Retrofit2.0
2 |
3 | >以下只是本人对`Retrofit2.0`基本使用方法的归纳整理,引做笔记
4 |
5 | ### GRADLE
6 |
7 | ```java
8 | compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
9 | ```
10 | `Retrofit`至少需要Java 7或者Android 2.3
11 |
12 | ### Introduction
13 |
14 | 改造将您的HTTP API到Java接口
15 |
16 | ```java
17 | public interface GitHubService {
18 | @GET("users/{user}/repos")
19 | Call> listRepos(@Path("user") String user);
20 | }
21 | ```
22 | `Retrofit`类对`GitHubService`接口的实现
23 |
24 | ```java
25 | Retrofit retrofit = new Retrofit.Builder()
26 | .baseUrl("https://api.github.com")
27 | .build();
28 |
29 | GitHubService service = retrofit.create(GitHubService.class);
30 | ```
31 | Here's an example of using the GsonConverterFactory class to generate an implementation of the GitHubService interface which uses Gson for its deserialization.
32 |
33 | ```java
34 | Retrofit retrofit = new Retrofit.Builder()
35 | .baseUrl("https://api.github.com")
36 | .addConverterFactory(GsonConverterFactory.create())
37 | .build();
38 |
39 | GitHubService service = retrofit.create(GitHubService.class);
40 | ```
41 |
42 | 每一个被`GitHubService`创建的`Call`可以同步或异步的HTTP请求发送到远程的网路服务器
43 |
44 | ```java
45 | Call> repos = service.listRepos("octocat");
46 | ```
47 | 这个`Call`对象是从你的`API`接口返回的参数化后的对象。调用跟接口名相同的函数名,你就会得到一个实例出来。我们可以直接调用它的`execute`方法,但是得留意一下,这个方法只能调用一次。
48 |
49 | - 另一个例子:
50 |
51 | ```java
52 | interface GitHubService {
53 | @GET("/repos/{owner}/{repo}/contributors")
54 | Call> repoContributors(
55 | @Path("owner") String owner,
56 | @Path("repo") String repo);
57 | }
58 |
59 | Call> call =
60 | gitHubService.repoContributors("square", "retrofit");
61 |
62 | response = call.execute();
63 |
64 | // This will throw IllegalStateException:
65 | response = call.execute();
66 |
67 | Call> call2 = call.clone();
68 | // This will not throw:
69 | response = call2.execute();
70 | ```
71 | 当你尝试调用第二次的时候,就会出现失败的错误。实际上,可以直接克隆一个实例,代价非常低。当你想要多次请求一个接口的时候,直接用`clone`的方法来生产一个新的,相同的可用对象吧。
72 |
73 | 想要实现异步,需要调用`enqueue`方法。现在,我们就能通过一次声明实现同步和异步了!
74 |
75 | ```java
76 | Call> call =
77 | gitHubService.repoContributors("square", "retrofit");
78 |
79 | call.enqueue(new Callback>() {
80 | @Override void onResponse(/* ... */) {
81 | // ...
82 | }
83 |
84 | @Override void onFailure(Throwable t) {
85 | // ...
86 | }
87 | });
88 | ```
89 |
90 | 当你将一些异步请求压入队列后,甚至你在执行同步请求的时候,你可以随时调用 cancel 方法来取消请求:
91 |
92 | ```java
93 | Call> call =
94 | gitHubService.repoContributors("square", "retrofit");
95 |
96 | call.enqueue( );
97 | // or...
98 | call.execute();
99 |
100 | // later...
101 | call.cancel();
102 | ```
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | > [Retrofit](http://square.github.io/retrofit/):A type-safe HTTP client for Android and Java
114 | 如果希望了解更多新特性可以查看以下网站:
115 | [用 Retrofit 2 简化 HTTP 请求](https://realm.io/cn/news/droidcon-jake-wharton-simple-http-retrofit-2/)
116 |
117 | ---
118 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
119 | - Good Luck!
--------------------------------------------------------------------------------
/Android进阶/Fragment中获取Context对象的最佳方案.md:
--------------------------------------------------------------------------------
1 | Fragment中获取Context对象的最佳方案
2 | ===
3 |
4 | ###Context类型
5 |
6 | >引用郭神的一段话:"Android程序不像Java程序一样,随便创建一个类,写个main()方法就能跑了,而是要有一个完整的Android工程环境,在这个环境下,我们有像Activity、Service、BroadcastReceiver等系统组件,而这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。"
7 |
8 | - Context一共有三种类型,分别是Application、Activity和Service。
9 |
10 | > 有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
11 |
12 | -getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了,如下所示:
13 |
14 | ```java
15 | public class MyReceiver extends BroadcastReceiver {
16 |
17 | @Override
18 | public void onReceive(Context context, Intent intent) {
19 | MyApplication myApp = (MyApplication) context.getApplicationContext();
20 | Log.d("TAG", "myApp is " + myApp);
21 | }
22 |
23 | }
24 | ```
25 | - `MyApplication`在构造方法中调用Context的方法就会崩溃,在onCreate()方法中调用Context的方法就一切正常
26 |
27 | ```java
28 | //正确使用方法
29 | public class MyApplication extends Application {
30 |
31 | @Override
32 | public void onCreate() {
33 | super.onCreate();
34 | String packageName = getPackageName();
35 | Log.d("TAG", "package name is " + packageName);
36 | }
37 |
38 | }
39 | ```
40 | - 其实这里我们只需谨记一点,Application全局只有一个,它本身就已经是单例了,无需再用单例模式去为它做多重实例保护了,代码如下所示:
41 |
42 | ```java
43 | public class MyApplication extends Application {
44 |
45 | private static MyApplication app;
46 |
47 | public static MyApplication getInstance() {
48 | return app;
49 | }
50 |
51 | @Override
52 | public void onCreate() {
53 | super.onCreate();
54 | app = this;
55 | }
56 |
57 | }
58 | ```
59 | ###Fragment中获取Context对象的最佳方案
60 | 1.MyApplication是一定要写的
61 | 2.写一个如下的BaseFragment类:所有的Fragment都继承这个BaseFragment,直接通过getContext()方法即可得到Context对象,当然实例化Dialog等需要依附于Activity的对象时,还是老老实实的getActivity()吧
62 |
63 | ```java
64 | public class BaseFragment extends Fragment
65 | {
66 | private Activity activity;
67 |
68 | public Context getContext(){
69 | if(activity == null){
70 | return MyApplication.getInstance();
71 | }
72 | return activity;
73 | }
74 |
75 | @Override
76 | public void onAttach(Activity activity) {
77 | super.onAttach(activity);
78 | activity = getActivity();
79 | }
80 | }
81 | ```
82 | 3.运用getContext()获取Context对象
83 |
84 | ```java
85 | public class FriendsFragment extends BaseFragment {
86 |
87 | private Context mContext;
88 |
89 | public FriendsFragment() {
90 | // Required empty public constructor
91 | }
92 |
93 | @Override
94 | public void onCreate(Bundle savedInstanceState) {
95 | super.onCreate(savedInstanceState);
96 | mContext = this.getContext() ;
97 |
98 | }
99 | ......
100 | }
101 | ```
102 | ---
103 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
104 | - Good Luck!
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/Android基础/广播与服务day2笔记.md:
--------------------------------------------------------------------------------
1 | #服务两种启动方式
2 | * startService:服务被启动之后,跟启动它的组件没有一毛钱关系,属于服务进程
3 | * bindService:绑定服务,跟启动它的组件同生共死,不属于服务进程
4 | * 绑定服务和解绑服务的生命周期方法:onCreate->onBind->onUnbind->onDestroy
5 |
6 | ---
7 | #找领导办证
8 | * 把服务看成一个领导,服务中有一个banZheng方法,如何才能访问?
9 | * 绑定服务时,会触发服务的onBind方法,此方法会返回一个Ibinder的对象给MainActivity,通过这个对象访问服务中的方法
10 | * 绑定服务
11 |
12 | ```java
13 | Intent intent = new Intent(this, BanZhengService.class);
14 | bindService(intent, conn, BIND_AUTO_CREATE);
15 | ```
16 | * 绑定服务时要求传入一个ServiceConnection实现类的对象
17 | * 定义这个实现类
18 |
19 | ```java
20 | PublicBusiness zjr;
21 | ......
22 | class MyServiceconn implements ServiceConnection{
23 | //连接服务成功,此方法调用
24 | @Override
25 | public void onServiceConnected(ComponentName name, IBinder service) {
26 | zjr = (PublicBusiness) service;
27 | }
28 | @Override
29 | public void onServiceDisconnected(ComponentName name) {
30 | }
31 |
32 | }
33 | ```
34 | - 调用服务的办证方法
35 |
36 | ```java
37 | public void click(View v){
38 | zjr.QianXian();
39 | }
40 | ```
41 |
42 | * 创建实现类对象
43 |
44 | ```java
45 | conn = new MyServiceconn();
46 | ```
47 | * 在服务中定义一个类实现Ibinder接口,以在onBind方法中返回
48 |
49 | ```java
50 | public IBinder onBind (Intent intent){
51 | //返回一个Binder对象,这个对象就是中间人对象
52 | return new ZhongJianRen();
53 | }
54 |
55 | class ZhongJianRen extends Binder implements PublicBusiness{
56 | public void QianXian(){
57 | //访问服务中的banZheng方法
58 | BanZheng();
59 | }
60 | public void daMaJiang(){
61 | sop("陪李处打麻将");
62 | }
63 | }
64 |
65 | public void BanZheng(){
66 | sop("李处帮你办证");
67 | }
68 | ```
69 | * 把QianXian方法抽取到接口PublicBusiness中定义
70 |
71 | ```java
72 | public interface PublicBusiness{
73 | QianXian();
74 | }
75 | ```
76 | ---
77 | #两种启动方法混合使用
78 | * 用服务实现音乐播放时,因为音乐播放必须运行在服务进程中,可是音乐服务中的方法,需要被前台Activity所调用,所以需要混合启动音乐服务
79 | * 先start,再bind,销毁时先unbind,在stop
80 |
81 | ---
82 | ##使用服务注册广播接收者
83 | * Android四大组件都要在清单文件中注册
84 | * 广播接收者比较特殊,既可以在清单文件中注册,也可以直接使用代码注册
85 | - 广播一旦发出,系统就会去所以清单文件中寻找,哪个广播接收者的action和广播的action是匹配的,如果找到了,就把该广播接收者的进程启动起来。
86 | - 代码注册则是,需要使用广播接收者时,执行注册的代码,不需要时,执行解除注册的代码。
87 | * 有的广播接收者,必须代码注册
88 | * 电量改变
89 | * 屏幕锁屏和解锁
90 |
91 |
92 | * 注册广播接收者
93 |
94 | ```java
95 | //创建广播接收者对象
96 | receiver = new ScreenOnOffReceiver();
97 | //通过IntentFilter对象指定广播接收者接收什么类型的广播
98 | IntentFilter filter = new IntentFilter();
99 | filter.addAction(Intent.ACTION_SCREEN_OFF);
100 | filter.addAction(Intent.ACTION_SCREEN_ON);
101 |
102 | //注册广播接收者
103 | registerReceiver(receiver, filter);
104 | ```
105 | * 解除注册广播接收者
106 |
107 | ```java
108 | unregisterReceiver(receiver);
109 | ```
110 | * 解除注册之后,广播接收者将失去作用
111 |
112 | ##本地服务:服务和启动它的组件在同一个进程
113 | ##远程服务:服务和启动它的组件不在同一个进程
114 | * 远程服务只能隐式启动,类似隐式启动Activity,在清单文件中配置Service标签时,必须配置intent-filter子节点,并指定action子节点
115 |
116 | ```java
117 | Intent intent = new Intent();
118 | intent.setAction("");
119 | stateService(intent);
120 | ```
121 | #AIDL
122 | * Android interface definition language
123 | * 安卓接口定义语言
124 | * 作用:跨进程通信
125 | * 应用场景:远程服务中的中间人对象,其他应用是拿不到的,那么在通过绑定服务获取中间人对象时,就无法强制转换,使用aidl,就可以在其他应用中拿到中间人类所实现的接口
126 |
127 | 1 把远程服务的方法抽取成一个单独的接口Java文件
128 | 2 将接口后缀改成.aidl
129 | 3 在自动生成的PublicBusiness.java文件中,有一个静态抽象类Stub,它已经继承了binder类,实现了publicBusiness接口,这个类就是新的中间人
130 | 4 把aidl文件复制黏贴到06项目,黏贴的时候注意,aidl文件所在的包名必须跟05项目中aidl所在的包名一致
131 | 5 在06项目中,强转中间人对象时,直接使用Stub.asInterface()
132 |
133 | ##支付宝远程服务
134 | 1. 定义支付宝的服务,在服务中定义pay方法
135 | 2. 定义中间人对象,把pay方法抽取成接口
136 | 3. 把抽取出来的接口后缀名改成aidl
137 | 4. 中间人对象直接继承Stub对象
138 | 5. 注册这个支付宝服务,定义它的intent-Filter
139 |
140 | ##需要支付的应用
141 | 1. 把刚才定义好的aidl文件拷贝过来,注意aidl文件所在的包名必须跟原包名一致
142 | 2. 远程绑定支付宝的服务,通过onServiceConnected方法我们可以拿到中间人对象
143 | 3. 把中间人对象通过Stub.asInterface方法强转成定义了pay方法的接口
144 | 4. 调用中间人的pay方法
145 |
146 | ##五种前台进程
147 | 1. activity执行了onResume方法,获得焦点
148 | 3. 拥有一个服务执行了startForeground()方法
149 | 4. 拥有一个正在执行onCreate()、onStart()或者onDestroy()方法中的任意一个的服务
150 | 5. 拥有一个正在执行onReceive方法的广播接收者
151 |
152 | ##两种可见进程
153 | 1. activity执行了onPause方法,失去焦点,但是可见
154 | 2. 拥有一个跟可见或前台activity绑定的服务
155 |
156 | ---
157 | - 邮箱 :elisabethzhen@163.com
158 | - Good Luck!
--------------------------------------------------------------------------------
/Java基础/单例基础.md:
--------------------------------------------------------------------------------
1 |
2 | ### 懒汉式-线程不安全
3 |
4 | ```java
5 | public class Singleton{
6 | private static Singleton instance;
7 | private Singleton(){}
8 |
9 | public static Singleton getInstance() {
10 | if(instance == null)
11 | instance = new Singleton();
12 |
13 | return instance;
14 | }
15 | }
16 | ```
17 | 这种代码简单明了,而且使用了懒加载模式,但是存在这致命的问题,就是当有多个线程调用`getInstance()`时,就会创建多个对象,导致线程不安全。也就是说在多线程情况下是不能正常工作的。
18 |
19 | ### 懒汉式-线程不安全
20 |
21 | 为了解决多线程安全问题,加入同步
22 |
23 | ```java
24 |
25 | public static synchronized Singleton getInstance(){
26 | if (instance == null) {
27 | instance = new Singleton();
28 | }
29 | return instance;
30 | }
31 | ```
32 | 虽然解决了多实例的问题,但是,遗憾的是,效率很低,99%情况下不需要同步。因为在任何时候只能有一个线程调用 `getInstance()` 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。
33 |
34 |
35 | ### 双重检验锁
36 | 双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 `instance == null`,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 `if`,如果在同步块内不进行二次检验的话就会生成多个实例了。
37 |
38 |
39 | ```java
40 | public class Singleton{
41 | private static Singleton instance;
42 | private Singleton(){}
43 |
44 | public static Singleton getInstance(){
45 | if (instance == null) {
46 | synchronized(Singleton.class){
47 | if (instance == null) {
48 | instance = new Singleton();
49 | }
50 | }
51 | }
52 | return instance;
53 | }
54 | }
55 |
56 | ```
57 | 这段代码看起来很完美,很可惜,它是有问题。主要在于`instance = new Singleton()`这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
58 |
59 | 1.给 instance 分配内存
60 | 2.调用 Singleton 的构造函数来初始化成员变量
61 | 3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
62 |
63 | 但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
64 |
65 | 我们只需要将 instance 变量声明成 volatile 就可以了。
66 |
67 | ```java
68 | public class Singleton{
69 | private volatile static Singleton instance;
70 | private Singleton(){}
71 |
72 | public static Singleton getInstance(){
73 | if (instance == null) {
74 | synchronized(Singleton.class){
75 | if (instance == null) {
76 | instance = new Singleton();
77 | }
78 | }
79 | }
80 | return instance;
81 | }
82 | }
83 | ```
84 | 有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
85 |
86 | 但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。
87 |
88 | 相信你不会喜欢这种复杂又隐含问题的方式,当然我们有更好的实现线程安全的单例模式的办法。
89 |
90 | > 原子是化学元素可分割的最小单元,原子操作是操作可分割的最小单元。
91 |
92 | ### 饿汉式 static final field
93 |
94 | 这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
95 |
96 | ```java
97 | public class Singleton{
98 | private static final Singleton instance = new Singleton();
99 | private Singleton(){}
100 |
101 | public static Singleton getInstance(){
102 | return instance;
103 | }
104 | }
105 | ```
106 | 这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。
107 |
108 | 缺点:
109 |
110 | - 它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 `getInstance()`方法。
111 |
112 | - 饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 `getInstance()` 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
113 |
114 | 饿汉式变种:
115 |
116 | ```java
117 | public class Singleton{
118 | private Singleton instance = null;
119 | static{
120 | instance = new Singleton();
121 | }
122 | private Singleton(){}
123 |
124 | public static Singleton getInstance(){
125 | return this.instance;
126 | }
127 | }
128 | ```
129 | ### 静态内部类 static nested class
130 |
131 |
132 | ```java
133 | public class Singleton{
134 | private static class SingletonHolder{
135 | private static final Singleton INSTANCE = new Singleton();
136 | }
137 | private Singleton(){}
138 | public static final Singleton getInstance(){
139 | return SingletonHolder.INSTANCE;
140 | }
141 | }
142 | ```
143 |
144 | 这种写法仍然使用JVM本身机制保证了线程安全问题;由于 `SingletonHolder` 是私有的,除了 `getInstance()` 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
145 |
146 | ### 枚举 Enum
147 |
148 | ```java
149 | public enum Singleton{
150 | INSTANCE;
151 | public void whateverMethod(){
152 |
153 | }
154 | }
155 | ```
156 | 这种方式是Effective Java作者Josh Bloch 提倡的方式,我们可以通过`Singleton.INSTANCE`来访问实例,这比调用`getInstance()`方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
157 |
158 | ### 总结
159 |
160 | 一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。上述所说都是线程安全的实现,文章开头给出的第一种方法不算正确的写法。
161 |
162 | 一般情况下直接使用`饿汉式`就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用`静态内部类`,如果涉及到反序列化创建对象时会试着使用`枚举`的方式来实现单例。
163 |
164 |
--------------------------------------------------------------------------------
/Android进阶/Android开发艺术探索(研读笔记)/01-Activity的生命周期.md:
--------------------------------------------------------------------------------
1 | # Android开发艺术探索(研读笔记)
2 |
3 | >作者:[Dimon](https://dimon94.github.io/)
4 |
5 | >- 微博:[@Dimon-喰](http://weibo.com/dscott/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1)
6 | - GitHub:[@Dimon94](https://github.com/Dimon94)
7 | - LOFTER:[@Dimon、](http://dimon.lofter.com/)
8 |
9 |
10 | ## 01-Activity的生命周期
11 | > 生命周期和启动模式以及IntentFilter的匹配规则分析。
12 |
13 | Activity的生命周期分为两个部分
14 |
15 | 1. 典型情况下的生命周期
16 | 2. 异常情况下的生命周期
17 |
18 | ### 典型情况下的生命周期分析
19 |
20 | - **onCreate** :表示`Activity`正在被创建。在这里可以做一些初始化的工作。
21 | - **onRestart** :表示`Activity`正在重新启动。当当前`Activity`从不可见重新变成可见状态。
22 | - **onStart** :表示`Activity`正在被启动。已经可见,但不在前台,无法交互。
23 | - **onResume** :表示`Activity`已经可见,并且出现在前台可以交互。
24 | - **onPause** :表示`Activity`正在停止。在这里可以做一些储存数据,停止动画等工作,**但不能太耗时**,因为必须`onPause`执行完成之后新的`Activity`才能`Resume`。
25 | - **onStop** :表示`Activity`即将停止。可以进行一些稍微重量级的回收工作,不能太耗时。
26 | - **onDestroy** :表示`Activity`即将被销毁。可以进行一些回收工作和最终的资源释放。
27 |
28 |
29 | 
30 |
31 | **注意:**
32 | - **onStart和onStop是从Activity是否可见这个角度来回调的**
33 | - **onResum和onPause是从Activity是否在前台这个角度来回调的**
34 |
35 | ---
36 |
37 | ### 异常情况下的生命周期分析
38 |
39 | #### 情况 1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
40 |
41 | > 比如说横屏手机和竖屏手机会拿到两张不同的图片(设定了landscape或者portrait状态下的图片)。本来手机在竖屏状态,突然旋转屏幕,由于系统配置发生了变化,在默认情况下,Activity会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity。
42 |
43 | 当系统配置发生改变后,Activity会调用 onPause -> onStop -> onDestroy。
44 |
45 | 由于是异常情况终止,系统会在`onStop`之前调用`onSaveInstanceState`来保存当前`Activity`的状态。(与`onPause`没有时序关系)
46 |
47 | 当`Activity`被系统重新创建后,系统会调用`onRestoreInstanceState`,把之前`onSaveInstanceState`方法所保存的**`Bundle`对象**作为参数同时传给`onRestoreInstanceState`和`onCreate`方法。(从时序来说,`onRestoreInstanceState`的调用时机在`onStart`之后)
48 |
49 | 
50 |
51 | 而在视图方面,当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据。
52 |
53 | 其实每个View都有`onSaveInstanceState`和`onRestoreInstanceState`,关于保存和恢复View层级结构,系统的工作流程如下:
54 |
55 | 
56 |
57 | > onSaveInstanceState方法,系统只会在Activity即将被销毁**并且有机会重新显示**的情况下才会去调用它。
58 |
59 | #### 情况 2:资源内存不足导致低优先级的Activity被杀死
60 |
61 | 其实这种情况的数据存储与恢复过程与情况 1完全一致。
62 |
63 | Activity的优先级情况:
64 |
65 | - 前台的`Activity` —— 正在和用户交互的`Activity`,优先级最高
66 | - 可见但非前台的`Activity` —— 比如`Activity`中弹出了一个对话框,导致`Activity`可见但是位于后台,无法和用户进行直接交互
67 | - 后台的`Activity` —— 已经被暂停的`Activity`,比如执行了`onStop`,优先级最低
68 |
69 | 当系统内存不足时,系统就会按照上述优先级去杀死目标`Activity`所在的进程,并在后续通过`onSaveInstanceState`和`onRestoreInstanceState`来存储和恢复数据。而将后台工作放入`Service`中是一个比较好的方法。
70 |
71 | #### 当系统配置改变后,Activity如何不被重新创建
72 |
73 | 由于系统配置中有很多内容,如果当某项内容发生改变后,不想系统重新创建`Activity`,**可以给`Activity`指定`configChanges`属性**。
74 |
75 | ```
76 | android:configChanges="orientation|keyboardHidden"
77 | ```
78 |
79 | | 项目 | 含义 |
80 | | :------------ | :------------ |
81 | | mcc | SIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由3位数组成,中国为460.此项 标识mcc代码发生了改变 |
82 | | mnc | SIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03。此项标识mnc发生改变 |
83 | | **locale** | 设备的本地位置发生了改变们一般指切换了系统语言 |
84 | | touchscreen | 触摸屏发生了改变,正常情况下无法发生,可以忽略它 |
85 | | keyboard | 键盘类型发生了改变,比如用户使用了外插键盘 |
86 | | **keyboardHidden** | 键盘的可访问性发生了改变,比如用户调出了键盘 |
87 | | navigation | 系统导航方式发生了改变,比如采用了轨迹球导航,很难发生,可以忽略 |
88 | | screenLayout | 屏幕布局发生了改变,很可能是用户激活了另一个显示设备 |
89 | | fontScale | 系统字体缩放比如发生了改变,比如用户选择了一个新字号 |
90 | | uiMode | 用户界面模式发生了改变,比如是否开启了夜间模式(API8新添加) |
91 | | **orientation** | 屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕 |
92 | | screenSize | 当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化,这个选项比较特殊,它和编译选项有关,当编译选项中的minSdkVersion和targetSdkVersion 均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加) |
93 | | smallestScreenSize | 设备的物理屏幕尺寸发生了改变,这个项目和屏幕的方向没有关系,仅仅表示在实际的物理屏幕的尺寸改变的时候发生,比如用户切换到了外部的显示设备,这个选项和screenSize一样,当编译选项中的minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加) |
94 | | layoutDirection | 当布局方向发生变化,这个属性用的比较少,正常情况下无须修改布局的layoutDirection属性(API17新添加) |
95 |
96 | 如果我们没有在`Activity`的`configChanges`属性中指定该选项的话,当配置发生改变后就会导致`Activity`重新创建。
97 |
98 | 最常用的只有`locale`、`orientation`和`keyboardHidden`。
99 |
100 | 需要修改的代码很简单,只需要在AndroidMenifest.xml中加入Activity的声明即可:
101 |
102 | ```xml
103 |
107 |
108 |
109 |
110 |
111 |
112 | ```
113 | ```java
114 | @Override
115 | public void onConfigurationChanged(Configuration newConfig){
116 | super.onConfigurationChanged(newConfig);
117 | Log.d(TAG,"onConfigurationChanged,newOrientation:" + newConfig.orientation);
118 | }
119 | ```
120 |
121 | `Activity`没有重新创建,并且没有调用`onSaveInstanceState`和`onRestoreInstanceState`来存储和恢复数据,而是系统调用了`Activity`的`onConfigurationChanged`方法,这个时候我们可以加入一些自己的特殊处理了。
122 |
123 | ---
124 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
125 | - Good Luck!
126 |
--------------------------------------------------------------------------------
/Android基础/页面跳转和数据传递笔记.md:
--------------------------------------------------------------------------------
1 | #创建第二个Activity
2 | * 需要在清单文件中为其配置一个activity标签
3 | * 标签中如果带有这个子节点,则会在系统中多创建一个快捷图标
4 |
5 | ```xml
6 |
7 |
8 |
9 |
10 | ```
11 |
12 | * 一个应用程序可以在桌面创建多个快捷图标。
13 | * activity的名称、图标可以和应用程序的名称、图标不相同
14 |
15 | ```xml
16 | android:icon="@drawable/ic_launcher"
17 | android:label="@string/app_name"
18 | ```
19 | ---
20 | #Activity的跳转
21 | >Activity的跳转需要创建Intent对象,通过设置intent对象的参数指定要跳转Activity
22 | >
23 | >通过设置Activity的包名和类名实现跳转,称为显式意图
24 | >
25 | >通过指定动作实现跳转,称为隐式意图
26 | ###显式意图
27 | * 跳转至同一项目下的另一个Activity,直接指定该Activity的字节码即可
28 |
29 | ```java
30 | Intent intent = new Intent();
31 | intent.setClass(this, SecondActivity.class);
32 | startActivity(intent);
33 | ```
34 | * 跳转至其他应用中的Activity,需要指定该应用的包名和该Activity的类名
35 |
36 | ```java
37 | Intent intent = new Intent();
38 | //启动系统自带的拨号器应用
39 | intent.setClassName("com.android.dialer", "com.android.dialer.DialtactsActivity");
40 | startActivity(intent);
41 | ```
42 | ###隐式意图
43 | * 隐式意图跳转至指定Activity
44 |
45 | ```java
46 | Intent intent = new Intent();
47 | //启动系统自带的拨号器应用
48 | intent.setAction(Intent.ACTION_DIAL);
49 | startActivity(intent);
50 | ```
51 | * 要让一个Activity可以被隐式启动,需要在清单文件的activity节点中设置intent-filter子节点
52 |
53 | ```xml
54 |
55 |
56 |
57 |
58 |
59 | ```
60 |
61 | * action 指定动作(可以自定义,可以使用系统自带的)
62 | * data 指定数据(操作什么内容)
63 | * category 类别 (默认类别,机顶盒,车载电脑)
64 | * 隐式意图启动Activity,需要为intent设置以上三个属性,且值必须与该Activity在清单文件中对三个属性的定义匹配
65 | * intent-filter节点及其子节点都可以同时定义多个,隐式启动时只需与任意一个匹配即可
66 |
67 | #####获取通过setData传递的数据
68 |
69 | ```java
70 | //获取启动此Activity的intent对象
71 | Intent intent = getIntent();
72 | Uri uri = intent.getData();
73 | ```
74 | ###显式意图和隐式意图的应用场景
75 | * 显式意图用于启动同一应用中的Activity
76 | * 隐式意图用于启动不同应用中的Activity
77 | * 如果系统中存在多个Activity的intent-filter同时与你的intent匹配,那么系统会显示一个对话框,列出所有匹配的Activity,由用户选择启动哪一个
78 |
79 | ---
80 | #Activity跳转时的数据传递
81 | * Activity通过Intent启动时,可以通过Intent对象携带数据到目标Activity
82 |
83 | ```java
84 | Intent intent = new Intent(this, SecondActivity.class);
85 | intent.putExtra("maleName", maleName);
86 | intent.putExtra("femaleName", femaleName);
87 | startActivity(intent);
88 | ```
89 | * 在目标Activity中取出数据
90 |
91 | ```java
92 | Intent intent = getIntent();
93 | String maleName = intent.getStringExtra("maleName");
94 | String femaleName = intent.getStringExtra("femaleName");
95 | ```
96 | ---
97 | #Activity生命周期
98 | ###void onCreate()
99 | * Activity已经被创建完毕
100 |
101 | ###void onStart()
102 | * Activity已经显示在屏幕,但没有得到焦点
103 |
104 | ###void onResume()
105 | * Activity得到焦点,可以与用户交互
106 |
107 | ###void onPause()
108 | * Activity失去焦点,无法再与用户交互,但依然可见
109 |
110 | ###void onStop()
111 | * Activity不可见,进入后台
112 |
113 | ###void onDestroy()
114 | * Activity被销毁
115 |
116 | ###void onRestart()
117 | * Activity从不可见变成可见时会执行此方法
118 |
119 | ###使用场景
120 | * Activity创建时需要初始化资源,销毁时需要释放资源;或者播放器应用,在界面进入后台时需要自动暂停
121 |
122 | ###完整生命周期(entire lifetime)
123 | onCreate-->onStart-->onResume-->onPause-->onStop-->onDestory
124 |
125 | ###可视生命周期(visible lifetime)
126 | onStart-->onResume-->onPause-->onStop
127 |
128 | ###前台生命周期(foreground lifetime)
129 | onResume-->onPause
130 |
131 | ---
132 | #Activity的四种启动模式
133 | >每个应用会有一个Activity任务栈,存放已启动的Activity
134 | >
135 | >Activity的启动模式,修改任务栈的排列情况
136 |
137 | * standard 标准启动模式
138 | * singleTop 单一顶部模式
139 | * 如果任务栈的栈顶存在这个要开启的activity,不会重新的创建activity,而是复用已经存在的activity。保证栈顶如果存在,不会重复创建。
140 | * 应用场景:浏览器的书签
141 | * singeTask 单一任务栈,在当前任务栈里面只能有一个实例存在
142 | * 当开启activity的时候,就去检查在任务栈里面是否有实例已经存在,如果有实例存在就复用这个已经存在的activity,并且把这个activity上面的所有的别的activity都清空,复用这个已经存在的activity。保证整个任务栈里面只有一个实例存在
143 | * 应用场景:浏览器的activity
144 | * 如果一个activity的创建需要占用大量的系统资源(cpu,内存)一般配置这个activity为singletask的启动模式。webkit内核 c代码
145 |
146 | * singleInstance启动模式非常特殊, activity会运行在自己的任务栈里面,并且这个任务栈里面只有一个实例存在
147 | * 如果你要保证一个activity在整个手机操作系统里面只有一个实例存在,使用singleInstance
148 | * 应用场景: 电话拨打界面
149 |
150 | ---
151 | ##横竖屏切换的生命周期
152 | >默认情况下 ,横竖屏切换, 销毁当前的activity,重新创建一个新的activity
153 | >
154 | > 快捷键ctrl+F11
155 |
156 | 在一些特殊的应用程序常见下,比如游戏,不希望横竖屏切换activity被销毁重新创建
157 | 需求:禁用掉横竖屏切换的生命周期
158 | 1. 横竖屏写死
159 |
160 | ```
161 | android:screenOrientation="landscape"
162 | android:screenOrientation="portrait"
163 | ```
164 | 2. 让系统的环境 不再去敏感横竖屏的切换。
165 |
166 | ```
167 | android:configChanges="orientation|screenSize|keyboardHidden"
168 | ```
169 | ---
170 | #掌握开启activity获取返回值
171 | ###从A界面打开B界面, B界面关闭的时候,返回一个数据给A界面
172 | 步骤:
173 | 1. 开启activity并且获取返回值
174 |
175 | ```java
176 | startActivityForResult(intent, 0);
177 | ```
178 | 2. 在新开启的界面里面实现设置数据的逻辑
179 |
180 | ```java
181 | Intent data = new Intent();
182 | data.putExtra("phone", phone);
183 | //设置一个结果数据,数据会返回给调用者
184 | setResult(0, data);
185 | finish();//关闭掉当前的activity,才会返回数据
186 | ```
187 | 3. 在开启者activity里面实现方法
188 |
189 | ```java
190 | onActivityResult(int requestCode, int resultCode, Intent data)
191 | //通过data获取返回的数据
192 | ```
193 | 4. 根据请求码和结果码确定业务逻辑
194 |
195 | ---
196 | - 邮箱 :elisabethzhen@163.com
197 | - Good Luck!
198 |
--------------------------------------------------------------------------------
/Android基础/第五章详解广播机制.md:
--------------------------------------------------------------------------------
1 | #第五章 全局大喇叭,详解广播机制
2 | ###广播接收器(Broadcast Receiver)
3 | - 标准广播:是一种完全异步执行的广播,广播一旦发送,所以广播接收器都会立刻同时接收到这条广播
4 | - 完全异步
5 | - 效率高
6 | - 没有先后顺序,无法截断
7 | - 有序广播:是一种同步执行的广播,广播发出后,同一时间只有一个广播接收器能够接收到这条广播信息,然后执行,然后继续传播
8 | - 同步执行
9 | - 效率不高
10 | - 有先后顺序,可以截断正在传递的广播
11 |
12 | ---
13 | ###接收系统广播
14 | ####动态注册监听网络变化
15 | - 定义一个内部类NetworkChangeReceiver,这个类继承自BroadcastReceiver的,并重写onReceive()方法
16 |
17 | ```java
18 | class NatworkChangeReceiver extends BroadcastReceiver{
19 |
20 | @Override
21 | public void onReceive(Context context, Intent intent) {
22 | Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
23 | }
24 | }
25 | ```
26 | - 在onCreate()方法,首先先创建一个IntentFilter的实例,添加action,再创建NatworkChangeReceiver实例,最后调用registerReceiver()方法进行注册
27 |
28 | ```java
29 | private IntentFilter intentFilter;
30 | private NatworkChangeReceiver natworkChangeReceiver;
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 | intentFilter = new IntentFilter();
37 | intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
38 | natworkChangeReceiver = new NatworkChangeReceiver();
39 | registerReceiver(natworkChangeReceiver,intentFilter);
40 | }
41 | ```
42 | - 记得,动态注册的广播接收器一定都要**取消注册**才行,这里我们是在onDestroy()方法调用unregisterReceiver()方法
43 |
44 | ####静态注册实现开机启动
45 | ```java
46 | public class BootCompleteReceiver extends BroadcastReceiver
47 | {
48 | @Override
49 | public void onReceive(Context context, Intent intent) {
50 | Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show();
51 | }
52 | }
53 | ```
54 | - 需要在AndroidManifest.xml将广播接收器的类名注册进去。
55 |
56 | ```xml
57 |
58 | ```
59 | .....
60 |
61 | ```xml
62 |
63 |
64 |
65 |
66 |
67 | ```
68 | ---
69 | ###发送标准广播
70 | - 接收自定义的广播
71 |
72 | ```java
73 | public class MyBroadcastReceiver extends BroadcastReceiver {
74 | @Override
75 | public void onReceive(Context context, Intent intent) {
76 | Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
77 | }
78 | }
79 |
80 | ```
81 | - 在AndroidManifest.xml中对这个广播接收器进行注册
82 |
83 | ```xml
84 |
85 |
86 |
87 |
88 |
89 | ```
90 | - 利用按键监听去触发发送自定义广播
91 |
92 | ```java
93 | Button button = (Button) findViewById(R.id.button);
94 | button.setOnClickListener(new View.OnClickListener() {
95 | @Override
96 | public void onClick(View v) {
97 | Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
98 | sendBroadcast(intent);
99 | }
100 | });
101 | ```
102 | ---
103 | ###发送有序广播
104 |
105 | ```java
106 | Button button = (Button) findViewById(R.id.button);
107 | button.setOnClickListener(new View.OnClickListener() {
108 | @Override
109 | public void onClick(View v) {
110 | Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
111 | //sendBroadcast(intent); 标准广播
112 | sendOrderedBroadcast(intent,null);//有序广播
113 | }
114 | });
115 | ```
116 | - 添加优先值
117 |
118 | ```xml
119 |
120 |
121 |
122 |
123 |
124 | ```
125 | - 截断广播
126 |
127 | ```java
128 | public class MyBroadcastReceiver extends BroadcastReceiver {
129 | @Override
130 | public void onReceive(Context context, Intent intent) {
131 | Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
132 | abortBroadcast();//截断广播
133 | }
134 | }
135 |
136 | ```
137 | ---
138 | ###使用本地广播
139 |
140 | - 获取LocalBroadcastManager实例
141 |
142 | ```java
143 | localBroadcastManager = LocalBroadcastManager.getInstance(this);//获取实例
144 | ......
145 |
146 | class LocalReceiver extends BroadcastReceiver{
147 |
148 | @Override
149 | public void onReceive(Context context, Intent intent) {
150 | Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
151 | }
152 | }
153 | ```
154 | - 注册本地广播监听器
155 |
156 | ```java
157 | intentFilter = new IntentFilter();
158 | intentFilter.addAction("com.example,broadcasttest.LOCAL_BROADCAST");
159 |
160 | localReceiver = new LocalReceiver();
161 | localBroadcastManager.registerReceiver(localReceiver,intentFilter);
162 |
163 | Button button = (Button) findViewById(R.id.button);
164 | button.setOnClickListener(new View.OnClickListener() {
165 | @Override
166 | public void onClick(View v) {
167 |
168 | Intent intent = new Intent("com.example,broadcasttest.LOCAL_BROADCAST");
169 | localBroadcastManager.sendBroadcast(intent);//发送本地广播
170 | }
171 | });
172 | ```
173 | - 取消注册
174 |
175 | ```java
176 | localBroadcastManager.unregisterReceiver(localReceiver);
177 | ```
178 | ---
179 | - 邮箱 :elisabethzhen@163.com
180 | - Good Luck!
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/Android基础/旧特性笔记.md:
--------------------------------------------------------------------------------
1 | #Fragment
2 | * 用途:在一个Activity里切换界面,切换界面时只切换Fragment里面的内容
3 | * 生命周期方法跟Activity一致,可以理解把其为就是一个Activity
4 | * 定义布局文件作为Fragment的显示内容
5 |
6 | ```java
7 | //此方法返回的View就会被显示在Fragment上
8 | @Override
9 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
10 | Bundle savedInstanceState) {
11 | // TODO Auto-generated method stub
12 | //用布局文件填充成一个View对象,返回出去,那么就显示在Fragment上了
13 | View v = inflater.inflate(R.layout.fragment01, null);
14 | return v;
15 | }
16 | ```
17 | * 把Fragment显示至指定ViewGroup中
18 |
19 | ```java
20 | //把fragment显示至界面
21 | //new出fragment对象
22 | Fragment01 fg = new Fragment01();
23 | FragmentManager fm = getFragmentManager();
24 | //开启事务
25 | FragmentTransaction ft = fm.beginTransaction();
26 | //把fragment对象显示到指定资源id的组件里面
27 | ft.replace(R.id.fl, fg);
28 | ft.commit();
29 | ```
30 | ###生命周期
31 | * fragment切换时旧fragment对象会销毁,新的fragment对象会被创建
32 | ###低版本兼容
33 | * 在support-v4.jar包中有相关api,也就是说fragment可以在低版本模拟器运行
34 |
35 | ---
36 | #动画
37 | ###帧动画F rameAnimation
38 | > 一张张图片不断的切换,形成动画效果
39 |
40 | * 在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长
41 |
42 | ```xml
43 |
44 |
45 |
46 |
47 |
48 | ```
49 | * 在屏幕上播放帧动画
50 |
51 | ```java
52 | ImageView iv = (ImageView) findViewById(R.id.iv);
53 | //把动画文件设置为imageView的背景
54 | iv.setBackgroundResource(R.drawable.animations);
55 | AnimationDrawable ad = (AnimationDrawable) iv.getBackground();
56 | //播放动画
57 | ad.start();
58 | ```
59 | ###补间动画
60 | * 原形态变成新形态时为了过渡变形过程,生成的动画就叫补间动画
61 | * 位移、旋转、缩放、透明
62 |
63 | #####位移:
64 | * 参数10指的是X的起点坐标,但不是指屏幕x坐标为10的位置,而是imageview的 真实X + 10
65 | * 参数150指的是X的终点坐标,它的值是imageview的 真实X + 150
66 |
67 | ```java
68 | //创建为位移动画对象,设置动画的初始位置和结束位置
69 | TranslateAnimation ta = new TranslateAnimation(10, 150, 20, 140);
70 | ```
71 | * x坐标的起点位置,如果相对于自己,传0.5f,那么起点坐标就是 真实X + 0.5 * iv宽度
72 | * x坐标的终点位置,如果传入2,那么终点坐标就是 真实X + 2 * iv的宽度
73 | * y坐标的起点位置,如果传入0.5f,那么起点坐标就是 真实Y + 0.5 * iv高度
74 | * y坐标的终点位置,如果传入2,那么终点坐标就是 真实Y + 2 * iv高度
75 |
76 | ```java
77 | TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
78 | ```
79 | * 动画播放相关的设置
80 |
81 | ```java
82 | //设置动画持续时间
83 | ta.setDuration(2000);
84 | //动画重复播放的次数
85 | ta.setRepeatCount(1);
86 | //动画重复播放的模式
87 | ta.setRepeatMode(Animation.REVERSE);
88 | //动画播放完毕后,组件停留在动画结束的位置上
89 | ta.setFillAfter(true);
90 | //播放动画
91 | iv.startAnimation(ta);
92 | ```
93 | #####缩放:
94 | * 参数0.1f表示动画的起始宽度是真实宽度的0.1倍
95 | * 参数4表示动画的结束宽度是真实宽度的4倍
96 | * 缩放的中心点在iv左上角
97 |
98 | ```java
99 | ScaleAnimation sa = new ScaleAnimation(0.1f, 4, 0.1f, 4);
100 | ```
101 | * 参数0.1f和4意义与上面相同
102 | * 改变缩放的中心点:传入的两个0.5f,类型都是相对于自己,这两个参数改变了缩放的中心点
103 | * 中心点x坐标 = 真实X + 0.5 * iv宽度
104 | * 中心点Y坐标 = 真实Y + 0.5 * iv高度
105 |
106 | ```java
107 | ScaleAnimation sa = new ScaleAnimation(0.1f, 4, 0.1f, 4, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
108 | ```
109 | #####透明:
110 | * 0为完全透明,1为完全不透明
111 |
112 | ```java
113 | AlphaAnimation aa = new AlphaAnimation(0, 0.5f);
114 | ```
115 | #####旋转:
116 | * 20表示动画开始时的iv的角度
117 | * 360表示动画结束时iv的角度
118 | * 默认旋转的圆心在iv左上角
119 |
120 | ```java
121 | RotateAnimation ra = new RotateAnimation(20, 360);
122 | ```
123 | * 20,360的意义和上面一样
124 | * 指定圆心坐标,相对于自己,值传入0.5,那么圆心的x坐标:真实X + iv宽度 * 0.5
125 | * 圆心的Y坐标:真实Y + iv高度 * 0.5
126 |
127 | ```java
128 | RotateAnimation ra = new RotateAnimation(20, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
129 | ```
130 | #####所有动画一起飞
131 |
132 | ```java
133 | //创建动画集合
134 | AnimationSet set = new AnimationSet(false);
135 | //往集合中添加动画
136 | set.addAnimation(aa);
137 | set.addAnimation(sa);
138 | set.addAnimation(ra);
139 | iv.startAnimation(set);
140 | ```
141 | ---
142 | #属性动画
143 | * 补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变
144 | ###位移:
145 | * 第一个参数target指定要显示动画的组件
146 | * 第二个参数propertyName指定要改变组件的哪个属性
147 | * 第三个参数values是可变参数,就是赋予属性的新的值
148 | * 传入0,代表x起始坐标:当前x + 0
149 | * 传入100,代表x终点坐标:当前x + 100
150 |
151 | ```java
152 | //具有get、set方法的成员变量就称为属性
153 | ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "translationX", 0, 100) ;
154 | ```
155 | ###缩放:
156 | * 第三个参数指定缩放的比例
157 | * 0.1是从原本高度的十分之一开始
158 | * 2是到原本高度的2倍结束
159 |
160 | ```java
161 | ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "scaleY", 0.1f, 2);
162 | ```
163 | ###透明:
164 | * 透明度,0是完全透明,1是完全不透明
165 |
166 | ```java
167 | ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "alpha", 0.1f, 1);
168 | ```
169 | ###旋转
170 | * rotation指定是顺时针旋转
171 | * 20是起始角度
172 | * 270是结束角度
173 |
174 | ```java
175 | ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "rotation", 20, 270);
176 | ```
177 | * 属性指定为rotationX是竖直翻转
178 | * 属性指定为rotationY是水平翻转
179 |
180 | ```java
181 | ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "rotationY", 20, 180);
182 | ```
183 | ###可变参数
184 | * 第三个参数可变参数可以传入多个参数,可以实现往回位移(旋转、缩放、透明)
185 |
186 | ```java
187 | ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "translationX", 0, 70, 30, 100) ;
188 | ```
189 | ###所有动画一起飞
190 |
191 | ```java
192 | //创建动画师集合
193 | AnimatorSet set = new AnimatorSet();
194 | //设置要播放动画的组件
195 | set.setTarget(bt);
196 | //所有动画有先后顺序的播放
197 | //set.playSequentially(oa, oa2, oa3, oa4);
198 | //所有动画一起播放
199 | set.playTogether(oa, oa2, oa3, oa4);
200 | set.start();
201 | ```
202 | ---
203 | - 邮箱 :elisabethzhen@163.com
204 | - Good Luck!
205 |
--------------------------------------------------------------------------------
/Android基础/AndroidProject思路汇总01.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: AndroidProject思路汇总
3 | ---
4 | #01.电话拨号器
5 |
6 | ###流程:
7 |
8 | 1. 绘制界面,一个**EditText**一个**Button**
9 | 2. 获取**ID**
10 | 3. 建立按键监听
11 | 4. 在监听中获取**EditText**的电话信息-
12 | 5. 创建Intent意图对象
13 |
14 | ###代码如下:
15 |
16 | ```java
17 | String phone = editText.getText().toString();
18 | //创建意图对象
19 | Intent intent = new Intent();
20 | //把动作封装到意图对象当中
21 | intent.setAction(Intent.ACTION_CALL);
22 | //设置打给谁
23 | intent.setData(Uri.parse("tel:" + phone));
24 | //把动作告诉系统
25 | startActivity(intent);
26 | ```
27 |
28 | ---
29 | #02.messenger
30 |
31 | ###需求:
32 |
33 | - 可以输入信息对方
34 | - 可以输入信息
35 | - 可以发生短信
36 |
37 | ###流程:
38 |
39 | 1. 绘制基本界面,两个EditText一个Button
40 | 2. 获取ID
41 | 3. 设置按键监听,获取电话号码以及传输信息
42 |
43 | ```java
44 | public void onClick(View v) {
45 | //拿到用户输入的号码和内容
46 | EditText mMsg = (EditText) findViewById(R.id.et_content);
47 | EditText mPhone = (EditText) findViewById(R.id.et_phone);
48 |
49 | String phone = mMsg.getText().toString();
50 | String content = mPhone.getText().toString();
51 |
52 | //1.获取短信管理器
53 | SmsManager sm = SmsManager.getDefault();
54 | //切割短信,把长短信分为若干个小短信
55 | ArrayList smss = sm.divideMessage(content);
56 | //2.创建一个PendingIntent
57 | PendingIntent pi = PendingIntent.getActivity(MainActivity.this,0,new Intent(),0);
58 |
59 | //3.发送短信
60 | for (String string : smss){
61 |
62 | sm.sendTextMessage(phone,null,string,pi, null);
63 | }
64 | }
65 | ```
66 |
67 | ---
68 | #03.inputOutput
69 |
70 | ###需求
71 |
72 | - 绘制登陆界面
73 | - 可以保存用户密码
74 |
75 | ###在内部存储空间中读写文件
76 | >小案例:用户输入账号密码,勾选“记住账号密码”,点击登录按钮,登录的同时持久化保存账号和密码
77 |
78 | #####1. 定义布局
79 |
80 | #####2. 完成按钮的点击事件
81 | * 弹土司提示用户登录成功
82 |
83 | ```java
84 | Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
85 | ```
86 | #####3. 拿到用户输入的数据
87 | * 判断用户是否勾选保存账号密码
88 |
89 | ```java
90 | CheckBox cb = (CheckBox) findViewById(R.id.cb);
91 | if(cb.isChecked()){
92 |
93 | }
94 | ```
95 | #####4. 开启io流把文件写入内部存储
96 | * 直接开启文件输出流写数据
97 |
98 | ```java
99 | //持久化保存数据
100 | File file = new File("data/data/com.itheima.rwinrom/info.txt");
101 | FileOutputStream fos = new FileOutputStream(file);
102 | fos.write((name + "##" + pass).getBytes());
103 | fos.close();
104 | ```
105 | * 读取数据前先检测文件是否存在
106 |
107 | ```java
108 | if(file.exists())
109 | ```
110 | * 读取保存的数据,也是直接开文件输入流读取
111 |
112 | ```java
113 | File file = new File("data/data/com.itheima.rwinrom/info.txt");
114 | FileInputStream fis = new FileInputStream(file);
115 | //把字节流转换成字符流
116 | BufferedReader br = new BufferedReader(new InputStreamReader(fis));
117 | String text = br.readLine();
118 | String[] s = text.split("##");
119 | ```
120 | * 读取到数据之后,回显至输入框
121 |
122 | ```java
123 | et_name.setText(s[0]);
124 | et_pass.setText(s[1]);
125 | ```
126 | * 应用只能在自己的包名目录下创建文件,不能到别人家去创建
127 | ###直接复制项目
128 | * 需要改动的地方:
129 | * 项目名字
130 | * 应用包名
131 | * R文件重新导包
132 |
133 | ###使用路径api读写文件
134 | * getFilesDir()得到的file对象的路径是data/data/com.itheima.rwinrom2/files
135 | * 存放在这个路径下的文件,只要你不删,它就一直在
136 | * getCacheDir()得到的file对象的路径是data/data/com.itheima.rwinrom2/cache
137 | * 存放在这个路径下的文件,当内存不足时,有可能被删除
138 |
139 | * 系统管理应用界面的清除缓存,会清除cache文件夹下的东西,清除数据,会清除整个包名目录下的东西
140 |
141 |
142 | ##在外部存储读写数据
143 | ###sd卡的路径
144 | * sdcard:2.3之前的sd卡路径
145 | * mnt/sdcard:4.3之前的sd卡路径
146 | * storage/sdcard:4.3之后的sd卡路径
147 |
148 | 最简单的打开sd卡的方式
149 |
150 | ```java
151 | File file = new File("sdcard/info.txt");
152 | ```
153 | * 写sd卡需要权限
154 |
155 |
156 |
157 | * 读sd卡,在4.0之前不需要权限,4.0之后可以设置为需要
158 |
159 |
160 |
161 | * 使用api获得sd卡的真实路径,部分手机品牌会更改sd卡的路径
162 |
163 | ```java
164 | Environment.getExternalStorageDirectory()
165 | ```
166 | * 判断sd卡是否准备就绪
167 |
168 | ```java
169 | if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
170 | ```
171 |
172 | ###获取sd卡剩余容量
173 | ```java
174 | protected void onCreate(Bundle savedInstanceState) {
175 | super.onCreate(savedInstanceState);
176 | setContentView(R.layout.activity_main);
177 |
178 | File path = Environment.getExternalStorageDirectory();
179 | StatFs stat = new StatFs(path.getPath());
180 | long blockSize;
181 | long totalBlocks;
182 | long availableBlocks;
183 |
184 | //获取当前系统版本的等级
185 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
186 | blockSize = stat.getBlockSizeLong();
187 | totalBlocks = stat.getBlockCountLong();
188 | availableBlocks = stat.getAvailableBlocksLong();
189 | }
190 | else{
191 | blockSize = stat.getBlockSize();
192 | totalBlocks = stat.getBlockCount();
193 | availableBlocks = stat.getAvailableBlocks();
194 | }
195 |
196 | TextView tv = (TextView) findViewById(R.id.tv);
197 | tv.setText(formatSize(availableBlocks * blockSize));//剩余容量
198 | }
199 |
200 | private String formatSize(long size) {
201 | return Formatter.formatFileSize(this, size);
202 | }
203 | ```
204 |
205 | ###SharedPreference
206 | >用SharedPreference存储账号密码
207 |
208 | * 往SharedPreference里写数据
209 |
210 | ```java
211 | //拿到一个SharedPreference对象
212 | SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
213 | //拿到编辑器
214 | Editor ed = sp.edit();
215 | //写数据
216 | ed.putString("name", name);
217 | ed.commit();
218 | ```
219 | * 从SharedPreference里取数据
220 |
221 | ```java
222 | SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
223 | //从SharedPreference里取数据
224 | String name = sp.getString("name", "");
225 | ```
226 |
227 | ---
228 | - 邮箱 :elisabethzhen@163.com
229 | - Good Luck!
--------------------------------------------------------------------------------
/Android进阶/Android加载图片方案-避免OOM.md:
--------------------------------------------------------------------------------
1 | # Android加载图片方案,避免OOM
2 |
3 | > 通过郭神博客整理笔记
4 |
5 | 通过下面的代码看出每个应用程序最高可用内存是多少。
6 |
7 | ```java
8 | int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
9 | Log.d("TAG", "Max memory is " + maxMemory + "KB");
10 | ```
11 |
12 | ### 高效加载大图片
13 | 压缩图片至其控件大小
14 |
15 | - `BitmapFactory`这个类用于创建`Bitmap`对象
16 | - SD卡中的图片可以使用`decodeFile`方法;
17 | - 网络上的图片可以使用`decodeStream`方法;
18 | - 资源文件中的图片可以使用`decodeResource`方法;
19 |
20 | 这些方法会尝试为已经构建的`bitmap`分配内存,这时就会很容易导致`OOM`出现。
21 | 这时,通过解析方法提供的一个可选的`BitmapFactory.Options`参数,将这个参数的`inJustDecodeBounds`属性设置为true就可以让解析方法禁止为`bitmap`分配内存,返回值也不再是一个`Bitmap`对象,而是null。
22 |
23 | 通过`BitmapFactory.Options`的`outWidth`、`outHeight`和`outMimeType`属性都会被赋值。让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。
24 |
25 | ```java
26 | BitmapFactory.Options options = new BitmapFactory.Options();
27 | options.inJustDecodeBounds = true;
28 | BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
29 | int imageHeight = options.outHeight;
30 | int imageWidth = options.outWidth;
31 | String imageType = options.outMimeType;
32 | ```
33 | 为了避免`OOM`异常,最好在解析每张图片的时候都先检查一下图片的大小
34 |
35 | 接下来要考虑以下几个因素:
36 | - 预估一下加载整张图片所需占用的内存。
37 | - 为了加载这一张图片你所愿意提供多少内存。
38 | - 用于展示这张图片的控件的实际大小。
39 | - 当前设备的屏幕尺寸和分辨率。
40 |
41 | 通过设置`BitmapFactory.Options`中`inSampleSize`的值就可以实现。
42 | 下面的方法可以根据传入的宽和高,计算出合适的`inSampleSize`值:
43 |
44 | ```java
45 | public static int calculateInSampleSize(BitmapFactory.Options options,
46 | int reqWidth, int reqHeight) {
47 | // 源图片的高度和宽度
48 | final int height = options.outHeight;
49 | final int width = options.outWidth;
50 | int inSampleSize = 1;
51 | if (height > reqHeight || width > reqWidth) {
52 | // 计算出实际宽高和目标宽高的比率
53 | final int heightRatio = Math.round((float) height / (float) reqHeight);
54 | final int widthRatio = Math.round((float) width / (float) reqWidth);
55 | // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
56 | // 一定都会大于等于目标的宽和高。
57 | inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
58 | }
59 | return inSampleSize;
60 | }
61 | ```
62 | 使用这个方法,首先你要将`BitmapFactory.Options`的`inJustDecodeBounds`属性设置为true,解析一次图片。
63 | 然后将`BitmapFactory.Options`连同期望的宽度和高度一起传递到到`calculateInSampleSize`方法中,就可以得到合适的`inSampleSize`值了。之后再解析一次图片,使用新获取到的`inSampleSize`值,并把`inJustDecodeBounds`设置为false,就可以得到压缩后的图片了。
64 |
65 | ```java
66 | public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
67 | int reqWidth, int reqHeight) {
68 | // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
69 | final BitmapFactory.Options options = new BitmapFactory.Options();
70 | options.inJustDecodeBounds = true;
71 | BitmapFactory.decodeResource(res, resId, options);
72 | // 调用上面定义的方法计算inSampleSize值
73 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
74 | // 使用获取到的inSampleSize值再次解析图片
75 | options.inJustDecodeBounds = false;
76 | return BitmapFactory.decodeResource(res, resId, options);
77 | }
78 | ```
79 | 下面的代码非常简单地将任意一张图片压缩成100*100的缩略图,并在ImageView上展示。
80 |
81 | ```java
82 | mImageView.setImageBitmap(
83 | decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
84 | ```
85 |
86 | ### 使用图片缓存技术
87 | 使用内存缓存技术可以让组件快速地重新加载和处理图片。
88 | 其中最核心的类是`LruCache` (此类在android-support-v4的包中提供) 。
89 | 这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用**强引用**存储在 `LinkedHashMap` 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
90 |
91 | > 在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
92 |
93 | 为了能够选择一个合适的缓存大小给`LruCache`, 有以下多个因素应该放入考虑范围内,例如:
94 |
95 | - 你的设备可以为每个应用程序分配多大的内存?
96 | - 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
97 | - 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
98 | - 图片的尺寸和大小,还有每张图片会占据多少内存空间。
99 | - 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
100 | - 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
101 |
102 | 下面是一个使用 `LruCache` 来缓存图片的例子:
103 |
104 | ```java
105 | private LruCache mMemoryCache;
106 |
107 | @Override
108 | protected void onCreate(Bundle savedInstanceState) {
109 | // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
110 | // LruCache通过构造函数传入缓存值,以KB为单位。
111 | int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
112 | // 使用最大可用内存值的1/8作为缓存的大小。
113 | int cacheSize = maxMemory / 8;
114 | mMemoryCache = new LruCache(cacheSize) {
115 | @Override
116 | protected int sizeOf(String key, Bitmap bitmap) {
117 | // 重写此方法来衡量每张图片的大小,默认返回图片数量。
118 | return bitmap.getByteCount() / 1024;
119 | }
120 | };
121 | }
122 |
123 | public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
124 | if (getBitmapFromMemCache(key) == null) {
125 | mMemoryCache.put(key, bitmap);
126 | }
127 | }
128 |
129 | public Bitmap getBitmapFromMemCache(String key) {
130 | return mMemoryCache.get(key);
131 | }
132 | ```
133 | 在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 `GridView` 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。
134 | 当向 `ImageView` 中加载一张图片时,首先会在 `LruCache` 的缓存中进行检查。如果找到了相应的键值,则会立刻更新`ImageView` ,否则开启一个后台线程来加载这张图片。
135 |
136 | ```java
137 | public void loadBitmap(int resId, ImageView imageView) {
138 | final String imageKey = String.valueOf(resId);
139 | final Bitmap bitmap = getBitmapFromMemCache(imageKey);
140 | if (bitmap != null) {
141 | imageView.setImageBitmap(bitmap);
142 | } else {
143 | imageView.setImageResource(R.drawable.image_placeholder);
144 | BitmapWorkerTask task = new BitmapWorkerTask(imageView);
145 | task.execute(resId);
146 | }
147 | }
148 | ```
149 | `BitmapWorkerTask` 还要把新加载的图片的键值对放到缓存中。
150 |
151 | ```java
152 | class BitmapWorkerTask extends AsyncTask {
153 | // 在后台加载图片。
154 | @Override
155 | protected Bitmap doInBackground(Integer... params) {
156 | final Bitmap bitmap = decodeSampledBitmapFromResource(
157 | getResources(), params[0], 100, 100);
158 | addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
159 | return bitmap;
160 | }
161 | }
162 | ```
163 | ---
164 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
165 | - Good Luck!
--------------------------------------------------------------------------------
/Android基础/内容提供者笔记.md:
--------------------------------------------------------------------------------
1 | #内容提供者ContentProvider-四大组件之一
2 | * 应用的数据库是不允许其他应用访问的
3 | * 内容提供者的作用就是让别的应用访问到你的数据库
4 | * 自定义内容提供者,继承ContentProvider类,重写增删改查方法,在方法中写增删改查数据库的代码,举例增方法
5 |
6 | ```java
7 | //此方法供其他应用调用,用于往数据库里插入数据
8 | //values:由其他应用传入,用于封装要插入的数据
9 | //uri:内容提供者的主机名,也就是地址
10 | @Override
11 | public Uri insert(Uri uri, ContentValues values) {
12 | db.insert("person", null, values);
13 | return uri;
14 | }
15 | ```
16 | * 在清单文件中定义内容提供者的标签,注意必须要有authorities属性,这是内容提供者的主机名,功能类似地址
17 |
18 | ```xml
19 |
23 | ```
24 |
25 | * 创建一个其他应用,访问自定义的内容提供者,实现对数据库的插入操作
26 |
27 | ```java
28 | public void click(View v){
29 | //得到内容分解器对象
30 | ContentResolver cr = getContentResolver();
31 | ContentValues cv = new ContentValues();
32 | cv.put("name", "小方");
33 | cv.put("phone", 138856);
34 | cv.put("money", 3000);
35 | //url:内容提供者的主机名
36 | cr.insert(Uri.parse("content://com.itheima.person"), cv);
37 | }
38 | ```
39 | ###UriMatcher
40 | * 用于判断一条uri跟指定的多条uri中的哪条匹配
41 | * 添加匹配规则
42 |
43 | ```java
44 | //创建uri匹配器对象
45 | static UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH);
46 | //指定多条uri
47 | static {
48 | um.addURI("com.itheima.person", "person", PERSON_CODE);
49 | um.addURI("com.itheima.person", "company", COMPANY_CODE);
50 | //#号可以代表任意数字
51 | um.addURI("com.itheima.person", "person/#", QUERY_ONE_PERSON_CODE);
52 | }
53 |
54 | ```
55 | * 通过Uri匹配器可以实现操作不同的表
56 |
57 | ```java
58 | @Override
59 | public Uri insert(Uri uri, ContentValues values) {
60 | if(um.match(uri) == PERSON_CODE){
61 | db.insert("person", null, values);
62 | }
63 | else if(um.match(uri) == COMPANY_CODE){
64 | db.insert("company", null, values);
65 | }
66 | else{
67 | throw new IllegalArgumentException("uri有问题喔么么哒");
68 | }
69 | return uri;
70 | }
71 | ```
72 | * 如果路径中带有数字,把数字提取出来的api
73 |
74 | ```java
75 | int id = (int) ContentUris.parseId(uri);
76 | ```
77 | ---
78 | #短信数据库
79 | * 只需要关注sms表
80 | * 只需要关注4个字段
81 | * body:短信内容
82 | * address:短信的发件人或收件人号码(跟你聊天那哥们的号码)
83 | * date:短信时间
84 | * type:1为收到,2为发送
85 | * 读取系统短信,首先查询源码获得短信数据库内容提供者的主机名和路径,然后
86 |
87 | ```java
88 | ContentResolver cr = getContentResolver();
89 | Cursor c = cr.query(Uri.parse("content://sms"), new String[]{"body", "date", "address", "type"}, null, null, null);
90 | while(c.moveToNext()){
91 | String body = c.getString(0);
92 | String date = c.getString(1);
93 | String address = c.getString(2);
94 | String type = c.getString(3);
95 | System.out.println(body+";" + date + ";" + address + ";" + type);
96 | }
97 | ```
98 | * 插入系统短信
99 |
100 | ```java
101 | ContentResolver cr = getContentResolver();
102 | ContentValues cv = new ContentValues();
103 | cv.put("body", "您尾号为XXXX的招行储蓄卡收到转账1,000,000人民币");
104 | cv.put("address", 95555);
105 | cv.put("type", 1);
106 | cv.put("date", System.currentTimeMillis());
107 | cr.insert(Uri.parse("content://sms"), cv);
108 | ```
109 | * 插入查询系统短信需要注册权限
110 |
111 | ```xml
112 |
117 | ```
118 | ---
119 | #联系人数据库
120 |
121 | * raw\_contacts表:
122 | * contact_id:联系人id
123 | * data表:联系人的具体信息,一个信息占一行
124 | * data1:信息的具体内容
125 | * raw\_contact_id:联系人id,描述信息属于哪个联系人
126 | * mimetype_id:描述信息是属于什么类型
127 | * mimetypes表:通过mimetype_id到该表查看具体类型
128 |
129 | ###读取联系人
130 | * 先查询raw\_contacts表拿到联系人id
131 |
132 | ```java
133 | Cursor cursor = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, null, null, null);
134 | ```
135 | * 然后拿着联系人id去data表查询属于该联系人的信息
136 |
137 | ```java
138 | Cursor c = cr.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1", "mimetype"}, "raw_contact_id = ?", new String[]{contactId}, null);
139 | ```
140 | * 得到data1字段的值,就是联系人的信息,通过mimetype判断是什么类型的信息
141 |
142 | ```java
143 | while(c.moveToNext()){
144 | String data1 = c.getString(0);
145 | String mimetype = c.getString(1);
146 | if("vnd.android.cursor.item/email_v2".equals(mimetype)){
147 | contact.setEmail(data1);
148 | }
149 | else if("vnd.android.cursor.item/name".equals(mimetype)){
150 | contact.setName(data1);
151 | }
152 | else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){
153 | contact.setPhone(data1);
154 | }
155 | }
156 | ```
157 | ###插入联系人
158 | * 先查询raw\_contacts表,确定新的联系人的id应该是多少
159 |
160 | ```java
161 | ContentResolver cr = getContentResolver();
162 | Cursor cursorContactId = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"_id"}, null ,null, null );
163 | //默认联系人ID为1
164 | int contact_id = 1;
165 | if (cursorContactId.moveToNext) {
166 | //拿到主键+1
167 | int _id = cursorContactId.getInt(0);
168 | contact_id = ++_id;
169 | }
170 | ```
171 | * 把确定的联系人id插入raw\_contacts表
172 |
173 | ```java
174 | cv.put("contact_id", contact_id);
175 | cr.insert(Uri.parse("content://com.android.contacts/raw_contacts"), cv);
176 | ```
177 | * 在data表插入数据
178 | * 插3个字段:data1、mimetype、raw\_contact_id
179 |
180 | ```java
181 | cv = new ContentValues();
182 | cv.put("data1", "赵六");
183 | cv.put("mimetype", "vnd.android.cursor.item/name");
184 | cv.put("raw_contact_id", _id);
185 | cr.insert(Uri.parse("content://com.android.contacts/data"), cv);
186 |
187 | cv = new ContentValues();
188 | cv.put("data1", "1596874");
189 | cv.put("mimetype", "vnd.android.cursor.item/phone_v2");
190 | cv.put("raw_contact_id", _id);
191 | cr.insert(Uri.parse("content://com.android.contacts/data"), cv);
192 | ```
193 | -----
194 | #内容观察者
195 | * 当数据库数据改变时,内容提供者会发出通知,在内容提供者的uri上注册一个内容观察者,就可以收到数据改变的通知
196 |
197 | ```java
198 | cr.registerContentObserver(Uri.parse("content://sms"), true, new MyObserver(new Handler()));
199 |
200 | class MyObserver extends ContentObserver{
201 |
202 | public MyObserver(Handler handler) {
203 | super(handler);
204 | // TODO Auto-generated constructor stub
205 | }
206 |
207 | //内容观察者收到数据库发生改变的通知时,会调用此方法
208 | @Override
209 | public void onChange(boolean selfChange) {
210 |
211 | }
212 |
213 | }
214 | ```
215 | * 在内容提供者中发通知的代码
216 |
217 | ```java
218 | ContentResolver cr = getContext().getContentResolver();
219 | //发出通知,所有注册在这个uri上的内容观察者都可以收到通知
220 | cr.notifyChange(uri, null);
221 | ```
222 | ---
223 | - 邮箱 :elisabethzhen@163.com
224 | - Good Luck!
225 |
--------------------------------------------------------------------------------
/Android进阶/Android开发艺术探索(研读笔记)/02-Activity的启动模式.md:
--------------------------------------------------------------------------------
1 | # Android开发艺术探索(研读笔记)
2 |
3 | >作者:[Dimon](https://dimon94.github.io/)
4 |
5 | >- 微博:[@Dimon-喰](http://weibo.com/dscott/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1)
6 | - GitHub:[@Dimon94](https://github.com/Dimon94)
7 | - LOFTER:[@Dimon、](http://dimon.lofter.com/)
8 |
9 | ## 02-Activity的启动模式
10 | ### Activity 的 LaunchMode
11 | > 复习一点:启动`Activity`时,系统会创造实例并把他们放入任务栈里,而任务栈是一种“后进先出”的栈结构。
12 |
13 | `Activity`的四种启动模式:
14 |
15 | - **standard**:标准模式、默认模式。每次启动一个`Activity`都会重新创建一个新的实例,不管这个实例是否已经存在。在这种模式下,某个`Activity`启动了一号`Activity`,那么一号`Activity`就运行在启动它的那个`Activity`所在的栈中。
16 |
17 | - **singleTop**:栈顶复用模式。如果新的`Activity`已经位于任务栈的栈顶,那么此`Activity`就不会被重新创建,同时它的`onNewIntent`方法会被回调,并且可以根据此方法的参数获得当前请求的信息。
18 |
19 | - **singleTask**:栈内复用模式。在这种单实例模式下,只要`Activity`在一个栈中存在,那么多次启动此`Activity`都不会重新创建实例,系统也会调用其`onNewIntent`。
20 |
21 | 
22 |
23 | - **singleInstance**:单实例模式。这是一种加强的singleTask模式,除了具有singleTask模式的所有特性外,还加强了一点,那就是**具体此种模式的`Activity`只能单独地位于一个任务栈中。**
24 |
25 | >注:在任何跳转的时候,首先调用本`Activity`的`onPause`,然后跳转。如果被跳转的`activity`由于启动方式而没创建新的实例,则会先调用`onNewIntent`,然后按照正常的生命周期调用。
26 |
27 | 如:
28 |
29 | 1. A→B,A:onPause;B:onCreate,onStart,onResume。
30 | 2. A(singleTop)→A,A:onPause;A:onSaveInstanceState;A:onResume。
31 |
32 | ### 一些具体问题与情况
33 |
34 | - 一:
35 |
36 | >首先要说明:任务栈分为前台任务栈和后台任务栈,后台任务栈中的`Activity`位于暂停状态。
37 |
38 | 当在前台任务栈AB启动Activity D,而D正好是后台任务栈CD的栈顶。现在假设后台任务栈里的Activity的启动模式是`singleTask`,请求启动D,那么整个后台任务栈都会被切换到前台,直接占据前台任务栈的栈顶,即ABCD。如果之前请求启动的是C,那么就会变成ABC。
39 |
40 | **`singleTask`模式的`Activity`切换到栈顶会导致在它之上的栈内的`Activity`出栈。**
41 |
42 | - 二:
43 |
44 | `TaskAffinity`:任务相关性。标识一个`Activity`所需要的任务栈的名字。
45 |
46 | ```
47 | adnroid:taskAffinity="com.dimon.task1"
48 | ```
49 | 默认情况下`Activity`所需要的任务栈的名字为应用的包名。
50 |
51 | 
52 |
53 | ### 如何给Activity指定启动模式呢?
54 |
55 | #### 第一种方法:通过`AndroidMenifest`为`Activity`指定启动模式。
56 |
57 | ```xml
58 |
64 | ```
65 |
66 | #### 第二种方法:通过在`Intent`中设置标志位来为`Activity`指定启动模式。
67 |
68 | ```java
69 | Intent intent = new Intent();
70 | intent.setClass(MainActivity.this, SecondActivity.class);
71 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
72 | startActivity(intent);
73 | ```
74 | #### 区别
75 |
76 | - 优先级:第二种方法比第一种优先级高,两种都存在时,以第二种为准。
77 | - 限定范围:第一种无法设置`FLAG_ACTIVITY_NEW_TASK`标识,而第二种无法指定`singleTask`模式。
78 |
79 | ### Acticity 中的一些比较常用的 Flags
80 |
81 | - **`FLAG_ACTIVITY_NEW_TASK`**
82 |
83 | 这个标记位的作用是为`Activity`指定“`singleTask`”启动模式,其效果和在XML中指定该启动模式相同。
84 |
85 | - **`FLAG_ACTIVITY_SINGLE_TOP`**
86 |
87 | 这个标记位的作用是为`Activity`指定“`singleTop`”启动模式,其效果和在XML中指定该启动模式相同。
88 |
89 | - **`FLAG_ACTIVITY_CLEAR_TOP`**
90 |
91 | 具有此标记位的`Activity`,当它启动时,在同一个任务栈中所有位于他上面的`Activity`都要出栈。`singleTask`默认就具有这个标记位的效果。如果被启动的`Activity`采用了`standard`启动模式,那么它连通它之上的`Activity`都要出栈,系统会创建新的`Activity`实例并放入栈顶。
92 |
93 | - **`FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS`**
94 |
95 | 具有这个标记的`Activity`不会出现在历史`Activity`的列表中,当某种情况下我们不希望用户通过历史列表回到我们的`Activity`的时候这个标记比较有用。它等同于在XML中指定Activity的属性`android:excludeFromRecents="true"`。
96 |
97 |
98 | ### IntentFilter的匹配规则
99 |
100 | #### action的匹配规则
101 |
102 | `action`的匹配要求`Intent`中的`action`存在且必须和过滤规则中的一个`action`相同。区分大小写。
103 |
104 | #### categoryd的匹配规则
105 |
106 | 如果`Intent`中含有`category`,那么所以的`category`都必须和过滤规则中的其中一个`category`相同。
107 |
108 | 如果`Intent`中不含有`category`,那么也能匹配,因为系统在调用`startActivity`或者`startActivityForResult`的时候会默认为`Intent`加上“`android.intent.category.DEFAULT`”这个`category`,所以为了我们的`Activity`能够接收隐式调用,就必须在`intent-filter`中指定“`android.intent.category.DEFAULT`”。
109 |
110 | #### data的匹配规则
111 |
112 | 规则与`action`相似。
113 |
114 | ```xml
115 |
116 |
117 | ...
118 |
119 | ```
120 | 以上规则说明`Intent`中的`mimeType`属性必须为“`image/*`”才能匹配。虽然没有定义`URI`,但是还是得有默认值,`URI`的默认值为`content`和`file`。也就是说没有`URI`也得在`Intent`中的`URI`部分的`schema`必须为`content`或者`file`才能匹配。
121 |
122 | ```java
123 | intent.setDataAndType(uri.parse("file://abc"),"image/png");
124 | ```
125 |
126 | 如果要为Intent指定完整的data,必须调用setDataAndType方法,不能先调用setData再调用setType。因为这两个方法会彼此清除对方的值。源码如下:
127 |
128 | ```java
129 | public Intent setData(Uri data){
130 | mData = data;
131 | mType = null;
132 | return this;
133 | }
134 |
135 | ```
136 |
137 | 而`data`的结构略微复杂,语法如下:
138 |
139 | ```xml
140 |
147 | ```
148 | `data`由两部分组成,`mimeType`和`URI`。
149 |
150 | `mimeType`指媒体类型,例如image/jpeg、audio/mpeg4-generic和video/*等。
151 |
152 | `URI`的结构如下:
153 |
154 | ```
155 | ://:/[||]
156 | ```
157 |
158 | - **Scheme**:URI的模式,比如http、file、content等。
159 | - **Host**:URI的主机名,比如www.baidu.com
160 | - **Port**:URI的端口号。
161 | - **Path、pathPattern和pathPrefix**:这三个表示路径信息,path完整路径信息;pathPattern也表示完整的路径信息,但是它里面可以包括通配符“*”,注意正则表达式;pathPrefix表示路径的前缀信息。
162 |
163 | ---
164 |
165 | #### IntentFilter整合例子
166 |
167 | ```xml
168 |
169 |
170 |
171 |
172 |
173 | \
174 |
175 |
176 |
177 | ```
178 |
179 | ```java
180 | Intent intent = new Intent("com.dimon.1");
181 | intent.addCategory("com.dimon.2b");
182 | intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
183 | startActivity(intent);
184 | ```
185 |
186 | #### Some Tips
187 | 使用隐式方式启动`Activity`时,最好做一下判断,看看是否有`Activity`能够匹配我们的隐式`Intent`。
188 |
189 | 方法有两个:采用`PackageManager`的`resolveActivity`方法或者`Intent`的`resolveActivity`方法,如果他们匹配不了就返回`null`。
190 |
191 | 另外`PackageManager`还提供了`queryIntentActivities`方法:它不是返回最佳匹配的`Activity`信息而是返回所有成功匹配的`Activity`信息。
192 |
193 | ```java
194 | public abstract List queryIntentActivities(Intent intent, int flags);
195 | public abstract ResolveInfo resolveActivity(Intent intent, int flags);
196 | ```
197 | 第二个参数我们使用`MATCH_DEFAULT_ONLY`这个标记位,这个标记位仅仅匹配那些在`intent-filter`中声明了``这个`category`的`Activity`。
198 | 只要上述两个方法不返回`null`,那么`startActivity`一定可以成功。因为不含有`DEFAULT`这个`category`的`Activity`是无法接收隐式`Intent`的。
199 |
200 |
201 |
202 | ---
203 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
204 | - Good Luck!
205 |
--------------------------------------------------------------------------------
/Android进阶/ListView适配器的使用技巧.md:
--------------------------------------------------------------------------------
1 | #Android ListView适配器应该这样写
2 | >文/ALIOUS(简书作者)
3 | 原文链接:http://www.jianshu.com/p/61b2841a8ab0?utm_campaign=hugo&utm_medium=reader_share&utm_content=note
4 | 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
5 |
6 | >ListView是我们开发中很重要的控件,在项目中也用的非常多,为了利用ListView展示数据,我们都需要给它新建一个适配器Adapter,一般继承于BaseAdapter,然后重写一些方法,其中最重要的方法是public View getView(int position, View convertView, ViewGroup parent),当然我们会依次做View的重用,还会利用ViewHolder缓存已经映射完成的子控件,避免重复查找映射。那么这样做,没有任何问题,但是每次都要写这些重复的代码,既没有效率也消磨我们的耐心,能不能进一步优化封装呢?
7 |
8 | ###ViewHolderHelper
9 |
10 | ViewHolder缓存View的思路我们还是要继续沿用,但是我们希望ViewHolderHelper能够适用所有Adapter,而且我们希望它能有更强大功能,例如能够设置显示的文本,能够设置点击事件等。
11 |
12 | ```java
13 | public class ViewHolderHelper {
14 |
15 | private SparseArray mViews;
16 | private Context mContext;
17 | private int position;
18 | private View mConvertView;
19 | private ViewHolderHelper(Context context, ViewGroup parent, int layoutId, int position){
20 | this.mContext = context;
21 | this.position = position;
22 | this.mViews = new SparseArray();
23 | this.mConvertView = LayoutInflater.from(mContext).inflate(layoutId, parent, false);
24 | mConvertView.setTag(this);
25 | }
26 | ....
27 | }
28 | ```
29 |
30 | 上面这段代码对一些属性进行初始化,类似于我们常规的setTag(ViewHolder),我们这里setTag(ViewHolderHelper),然后还要一个静态的方法供adapter获取可以复用的ViewHolderHelper,这是我们ViewHolder的核心思路。
31 |
32 | ```java
33 | public static ViewHolderHelper (Context context, View convertView, ViewGroup parent, layoutId , position){
34 | if (convertView == ){
35 | return ViewHolderHelper(context, parent, layoutId, position);
36 | }
37 | ViewHolderHelper existingHelper = (ViewHolderHelper)convertView.getTag();
38 | existingHelper.position = position; return existingHelper;
39 | }
40 | ```
41 | 目前为止就是ViewHolderHelper的缓存和读取,都是常规处理,不赘述。接下来我们需要添加一些方法来设置View的常用属性。那么我们首先要提供一个findViewById方法。
42 |
43 | ```java
44 | private T findViewById(int viewId){
45 | View view = mViews.get(viewId);
46 | if (view == null) {
47 | view = mConvertView.findViewById(viewId);
48 | mViews.put(viewId, view);
49 | }
50 | return (T)view;
51 | }
52 | ```
53 | 那么接下来就可以添加类似setText, setBackground(), setImageFromUrl, setOnClickListener()等方法,大家可以根据需要进行扩展。
54 |
55 | ```java
56 |
57 | public ViewHolderHelper setText(int viewId, String value){
58 | TextView view = findViewById(viewId);
59 | view.setText(value);
60 | return this;
61 | }
62 |
63 | public ViewHolderHelper setBackground(int viewId, int resId){
64 | View view = findViewById(viewId);
65 | view.setBackgroundResource(resId);
66 | return this;
67 | }
68 | public ViewHolderHelper setImageFromUrl(int viewId, String url){
69 | ImageView imageView = findViewById(viewId);
70 | Picasso.with(mContext).load(url).into(imageView);
71 | return this;
72 | }
73 |
74 | public ViewHolderHelper setClickListener(int viewId, View.OnClickListener listener){
75 | View view = findViewById(viewId);
76 | view.setOnClickListener(listener);
77 | return this;
78 | }
79 | ```
80 |
81 | 这里我们所有的`set`方法都返回`ViewHolderHelper`对象本身,这样做的好处就是如果我们要调用多次这样的`set`方法,我们就可以用.把它们连接起来,写法上看起来更简洁。
82 |
83 | ```java
84 | viewHolderHelper
85 | .setText(R.id.text, "base adapter")
86 | .setBackground(R.id.imageView, R.drawable.default_ic)
87 | .setClickListener(R.id.btn, new View.OnClickListener(){})...
88 | ```
89 | 还有一个细节,我们存储View与它们的id之间的映射,用的是`SparseArray`类,它比`HashMap`要更高效,大家可以去深挖一下,这里不展开了。
90 |
91 | ###AdapterBase
92 | 我们下面来实现`AdapterBase`,继承与`BaseAdapter`
93 |
94 | ```java
95 | public abstract class AdapterBase extends BaseAdapter {
96 | protected final Context mContext;
97 | protected List mData;
98 | protected final int [] mLayoutResArrays;
99 |
100 | public AdapterBase(Context context, int [] layoutResArrays){
101 | this(context, layoutResArrays, null);
102 | }
103 |
104 | public AdapterBase(Context context, int [] layoutResArrays, List data){
105 | this.mData = data == null ? new ArrayList() : data;
106 | this.mContext = context;
107 | this.mLayoutResArrays = layoutResArrays;
108 | }
109 |
110 | public void setData(ArrayList data){
111 | this.mData = data;
112 | this.notifyDataSetChanged();
113 | }
114 |
115 | public void addData(ArrayList data){
116 | if(data != null){
117 | this.mData.addAll(data);
118 | }
119 |
120 | this.notifyDataSetChanged();
121 | }
122 |
123 | public void addData(T data) {
124 | this.mData.add(data);
125 | this.notifyDataSetChanged();
126 | }
127 |
128 | public ArrayList getAllData() {
129 | return (ArrayList)this.mData;
130 | }
131 |
132 | @Override
133 | public int getCount() {
134 | if(this.mData == null){
135 | return 0;
136 | }
137 | return this.mData.size();
138 | }
139 |
140 | @Override
141 | public T getItem(int position) {
142 | if(position > this.mData.size()){
143 | return null;
144 | }
145 | return mData.get(position);
146 | }
147 |
148 | @Override
149 | public long getItemId(int position) {
150 | return position;
151 | }
152 | ...
153 | }
154 | ```
155 | 上面都是经常写代码,都能看明白。接下来我们重写`ListAdapter`的分组方法,毕竟还是有很多时候`ListView`的`cell`样式不止一种。
156 |
157 | ```java
158 | @Override
159 | public int getViewTypeCount() {
160 | return this.mLayoutResArrays.length;
161 | }
162 |
163 | /**
164 | * You should always override this method,to return the
165 | * correct view type for every cell.
166 | *
167 | */
168 | public int getItemViewType(int position){
169 | return 0;
170 | }
171 | ```
172 | 这里`getItemViewType`我们默认返回0 ,实际业务子类需要根据需求进行重写。接下来是最重要的`getView`方法。
173 |
174 | ```java
175 | @Override
176 | public View getView(int position, View convertView, ViewGroup parent) {
177 | final ViewHolderHelper helper = getAdapterHelper(position, convertView, parent);
178 | T item = getItem(position);
179 | convert(helper, item);
180 | return helper.getView();
181 | }
182 |
183 | protected abstract void convert(ViewHolderHelper helper, T item);
184 | protected abstract ViewHolderHelper getAdapterHelper(int position, View convertView, ViewGroup parent);
185 | ```
186 | `getView`方法里,我们先获取`ViewHolderHelper`对象,然后根据`position`获取数据实体对象,最后调用我们暴露给业务子类的`convert`接口对我们的`ListView cell`进行定制填充。
187 |
188 | ###总结
189 | 有了`AdapterBase`,让我们新建新的`Adapter`子类轻松愉快到没朋友,我们会类似于下面这样的去组织代码,而且也只需要这么多代码。
190 |
191 | ```java
192 | BaseAdapter listAdapter = new AdapterBase(context, new int [] {R.layout.layout1, R.layout.layout2, R.layout.layout3}){
193 |
194 | public int getItemViewType(int position){
195 | Bean bean = getItem(position);
196 | return bean.getViewType();
197 | }
198 | protected abstract void convert(ViewHolderHelper helper, T item) {
199 | helper
200 | .setText(R.id.text, "base adapter")
201 | .setBackground(R.id.imageView, R.drawable.default_ic)
202 | .setClickListener(R.id.btn, new View.OnClickListener(){});
203 | }
204 | }
205 | //set listview adapter
206 | mListView.setAdapter(listAdapter);
207 | ```
208 | Github上面有类似的成熟项目 https://github.com/JoanZapata/base-adapter-helper ,但是这个项目不支持多样式cell分组, 大家使用时要注意。最后我还是想多说一句,了解开源项目背后的实现原理非常必要,更重要的是能够在原有的基础上进行改进创新,更难能可贵,在这个过程中,我们自己也会得到提升。
209 |
210 | ---
211 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
212 | - Good Luck!
--------------------------------------------------------------------------------
/Android基础/广播与服务day1笔记.md:
--------------------------------------------------------------------------------
1 | #广播
2 | * 广播的概念
3 | * 现实:电台通过发送广播发布消息,买个收音机,就能收听
4 | * Android:系统在产生某个事件时发送广播,应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件。
5 | Android系统在运行的过程中,会产生很多事件,比如开机、电量改变、收发短信、拨打电话、屏幕解锁
6 |
7 | ---
8 | #IP拨号器
9 | > 原理:接收拨打电话的广播,修改广播内携带的电话号码
10 | * 定义广播接收者接收打电话广播
11 |
12 | public class CallReceiver extends BroadcastReceiver {
13 |
14 | //当广播接收者接收到广播时,此方法会调用
15 | @Override
16 | public void onReceive(Context context, Intent intent) {
17 | //拿到用户拨打的号码
18 | String number = getResultData();
19 | //修改广播内的号码
20 | setResultData("17951" + number);
21 | }
22 | }
23 | * 在清单文件中定义该广播接收者接收的广播类型
24 |
25 |
26 |
27 |
28 |
29 |
30 | * 接收打电话广播需要权限
31 |
32 |
33 | * 即使广播接收者的进程没有启动,当系统发送的广播可以被该接收者接收时,系统会自动启动该接收者所在的进程
34 |
35 | ---
36 | #短信拦截器
37 | >系统收到短信时会产生一条广播,广播中包含了短信的号码和内容
38 |
39 | * 定义广播接收者接收短信广播
40 |
41 | ```java
42 | public void onReceive(Context context, Intent intent) {
43 | //拿到广播里携带的短信内容
44 | Bundle bundle = intent.getExtras();
45 | Object[] objects = (Object[]) bundle.get("pdus");
46 | for(Object ob : objects ){
47 | //通过object对象创建一个短信对象
48 | SmsMessage sms = SmsMessage.createFromPdu((byte[])ob);
49 | System.out.println(sms.getMessageBody());
50 | System.out.println(sms.getOriginatingAddress());
51 | }
52 | }
53 | ```
54 | * 系统创建广播时,把短信存放到一个数组,然后把数据以pdus为key存入bundle,再把bundle存入intent
55 | * 清单文件中配置广播接收者接收的广播类型,注意要设置优先级属性,要保证优先级高于短信应用,才可以实现拦截
56 |
57 | ```xml
58 |
59 |
60 |
61 |
62 |
63 | ```
64 | * 添加权限
65 |
66 | ```xml
67 |
68 | ```
69 | * 4.0以后广播接收者安装以后必须手动启动一次,否则不生效
70 | * 4.0以后广播接收者如果被手动关闭,就不会再启动了
71 |
72 | ---
73 | #监听SD卡状态
74 | * 清单文件中定义广播接收者接收的类型,监听SD卡常见的三种状态,所以广播接收者需要接收三种广播
75 |
76 | ```xml
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | ```
86 | * 广播接收者的定义
87 |
88 | ```java
89 | public class SDCardReceiver extends BroadcastReceiver {
90 | @Override
91 | public void onReceive(Context context, Intent intent) {
92 | // 区分接收到的是哪个广播
93 | String action = intent.getAction();
94 |
95 | if(action.equals("android.intent.action.MEDIA_MOUNTED")){
96 | System.out.println("sd卡就绪");
97 | }
98 | else if(action.equals("android.intent.action.MEDIA_UNMOUNTED")){
99 | System.out.println("sd卡被移除");
100 | }
101 | else if(action.equals("android.intent.action.MEDIA_REMOVED")){
102 | System.out.println("sd卡被拔出");
103 | }
104 | }
105 | }
106 | ```
107 | ---
108 | #勒索软件
109 | * 接收开机广播,在广播接收者中启动勒索的Activity
110 | * 清单文件中配置接收开机广播
111 |
112 | ```xml
113 |
114 |
115 |
116 |
117 |
118 | ```
119 | * 权限
120 |
121 | ```xml
122 |
123 | ```
124 | * 定义广播接收者
125 |
126 | ```java
127 | @Override
128 | public void onReceive(Context context, Intent intent) {
129 | //开机的时候就启动勒索软件
130 | Intent it = new Intent(context, MainActivity.class);
131 | context.startActivity(it);
132 | }
133 | ```
134 | * 以上代码还不能启动MainActivity,因为广播接收者的启动,并不会创建任务栈,那么没有任务栈,就无法启动activity
135 | * 手动设置创建新任务栈的flag
136 |
137 | ```java
138 | it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
139 | ```
140 | ---
141 | #监听应用的安装、卸载、更新
142 | > 原理:应用在安装卸载更新时,系统会发送广播,广播里会携带应用的包名
143 | * 清单文件定义广播接收者接收的类型,因为要监听应用的三个动作,所以需要接收三种广播
144 |
145 | ```xml
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | ```
155 | * 广播接收者的定义
156 |
157 | ```java
158 | public void onReceive(Context context, Intent intent) {
159 | //区分接收到的是哪种广播
160 | String action = intent.getAction();
161 | //获取广播中包含的应用包名
162 | Uri uri = intent.getData();
163 | if(action.equals("android.intent.action.PACKAGE_ADDED")){
164 | System.out.println(uri + "被安装了");
165 | }
166 | else if(action.equals("android.intent.action.PACKAGE_REPLACED")){
167 | System.out.println(uri + "被更新了");
168 | }
169 | else if(action.equals("android.intent.action.PACKAGE_REMOVED")){
170 | System.out.println(uri + "被卸载了");
171 | }
172 | }
173 | ```
174 | ---
175 | #广播的两种类型
176 | * 无序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,并且是没有先后顺序(同时收到)
177 | * 有序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,但是会按照广播接收者的优先级来决定接收的先后顺序
178 | * 优先级的定义:-1000~1000
179 | * 最终接收者:所有广播接收者都接收到广播之后,它才接收,并且一定会接收
180 | * abortBroadCast:阻止其他接收者接收这条广播,类似拦截,只有有序广播可以被拦截
181 |
182 | ---
183 | #Service
184 | * 就是默默运行在后台的组件,可以理解为是没有前台的activity,适合用来运行不需要前台界面的代码
185 | * 服务可以被手动关闭,不会重启,但是如果被自动关闭,内存充足就会重启
186 | * startService启动服务的生命周期
187 | * onCreate-onStartCommand-onDestroy
188 | * 重复的调用startService会导致onStartCommand被重复调用
189 |
190 | ---
191 | # 进程优先级
192 | 1. 前台进程:拥有前台activity(onResume方法被调用)
193 | 2. 可见进程:拥有可见activity(onPause方法被调用)
194 | 3. 服务进程:不到万不得已不会被回收,而且即便被回收,内存充足时也会被重启
195 | 4. 后台进程:拥有后台activity(activity的onStop方法被调用了),很容易被回收
196 | 5. 空进程:没有运行任何activity,很容易被回收
197 |
198 | ---
199 | #电话窃听器
200 | * 电话状态:空闲、响铃、接听
201 | * 获取电话管理器,设置侦听
202 |
203 | ```java
204 | TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
205 | tm.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);//侦听电话状态改变
206 | * 侦听对象的实现
207 |
208 | class MyPhoneStateListener extends PhoneStateListener{
209 |
210 | //当电话状态改变时,此方法调用
211 | @Override
212 | public void onCallStateCcchanged(int state, String incomingNumber) {
213 | // TODO Auto-generated method stub
214 | super.onCallStateChanged(state, incomingNumber);
215 | switch (state) {
216 | case TelephonyManager.CALL_STATE_IDLE://空闲
217 | if(recorder != null){
218 | recorder.stop();
219 | recorder.release();
220 | recorder = null;
221 | }
222 | break;
223 | case TelephonyManager.CALL_STATE_OFFHOOK://摘机
224 | if(recorder != null){
225 | recorder.start();
226 | }
227 | break;
228 | case TelephonyManager.CALL_STATE_RINGING://响铃
229 | recorder = new MediaRecorder();
230 | //设置声音来源
231 | recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
232 | //设置音频文件格式
233 | recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
234 | recorder.setOutputFile("sdcard/haha.3gp");
235 | //设置音频文件编码
236 | recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
237 | try {
238 | recorder.prepare();
239 | } catch (IllegalStateException e) {
240 | // TODO Auto-generated catch block
241 | e.printStackTrace();
242 | } catch (IOException e) {
243 | // TODO Auto-generated catch block
244 | e.printStackTrace();
245 | }
246 | break;
247 | }
248 | }
249 | }
250 | ```
251 | ---
252 | - 邮箱 :elisabethzhen@163.com
253 | - Good Luck!
--------------------------------------------------------------------------------
/Android进阶/Android动画/FAB动画以及RecycleView载入动画.md:
--------------------------------------------------------------------------------
1 | ## FAB 动画
2 | 首先,我们需要把FAB按钮移到屏幕下边去,我在Fragment的`onActivityCreated`方法中加入
3 |
4 | ```java
5 | mFabButton.setTranslationY(2 * getResources().getDimensionPixelOffset(R.dimen.btn_fab_size));//R.dimen.btn_fab_size =
6 | ```
7 |
8 | 然后我在从网络获取到数据后,开始FAB显示动画
9 |
10 | ```java
11 | mFabButton.animate()
12 | .translationY(0)
13 | .setInterpolator(new OvershootInterpolator(1.f))
14 | .setStartDelay(500)
15 | .setDuration(ANIM_DURATION_FAB) //ANIM_DURATION_FAB = 400
16 | .start();
17 | ```
18 |
19 | 在delay 500秒后开始动画,主要是因为要与列表项的加载动画同步。
20 | 使用OvershootInterpolator修饰动画,可以实现在向上移动超出原来位置一定值后,再返回到原位置的效果。
21 |
22 | ## RecycleView加载动画
23 |
24 | 列表我是用RecycleView实现,我们需要在Adapter里加入动画处理
25 |
26 | 在`onBindViewHolder`方法中加入
27 |
28 | ```java
29 | runEnterAnimation(holder.itemView, position);
30 | ```
31 | 然后我们实现runEnterAnimation方法
32 |
33 | ```java
34 | private void runEnterAnimation(View view, int position) {
35 | if (!animateItems || position >= 3) { //3为屏幕显示Item数,animateItems = false 标志位,更新时变true
36 | return;
37 | }
38 |
39 | if (position > lastAnimatedPosition) { //lastAnimatedPosition = -1
40 | lastAnimatedPosition = position;
41 | view.setTranslationY(Utils.getScreenHeight(getActivity()));
42 | view.animate()
43 | .translationY(0)
44 | .setStartDelay(100 * position)
45 | .setInterpolator(new DecelerateInterpolator(3.f))
46 | .setDuration(700)
47 | .start();
48 | }
49 | }
50 | ```
51 | 列表加载动画,不是所有项都需要的,比如我这个例子中,屏幕上只能显示3个Item,
52 | 所以`position >= 3`就不用加入动画了。
53 |
54 | 先将Item向下移出屏幕
55 | `view.setTranslationY(Utils.getScreenHeight(getActivity()));`
56 |
57 | 接着根据Item的position设定delay时间,这样可以实现Item一个一个出现,
58 | 加入`DecelerateInterpolator` 可以实现动画先快后慢的效果。
59 |
60 | ### 滚动FAB的消失显示动画
61 | 创建一个继承自`FloatingActionButton`名为`ScrollAwareFABBehavior.java`的类。目前FAB默认的Behavior是为Snackbar让出空间,继承这个behavior,就可以处理垂直方向上的滚动事件:
62 |
63 | ```java
64 | public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
65 |
66 | private static final android.view.animation.Interpolator INTERPOLATOR =
67 | new FastOutSlowInInterpolator();
68 | private boolean mIsAnimatingOut = false;
69 |
70 |
71 | public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
72 | super();
73 | }
74 |
75 |
76 | @Override
77 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
78 | FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
79 | return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
80 | super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
81 | nestedScrollAxes);
82 | }
83 |
84 | @Override
85 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child,
86 | View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
87 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
88 | dyUnconsumed);
89 |
90 | if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
91 | animateOut(child);
92 | } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
93 | animateIn(child);
94 | }
95 | }
96 |
97 | // Same animation that FloatingActionButton.Behavior uses to
98 | // hide the FAB when the AppBarLayout exits
99 | private void animateOut(final FloatingActionButton button) {
100 | if (Build.VERSION.SDK_INT >= 14) {
101 | ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F)
102 | .setInterpolator(INTERPOLATOR).withLayer()
103 | .setListener(new ViewPropertyAnimatorListener() {
104 | public void onAnimationStart(View view) {
105 | ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
106 | }
107 |
108 | public void onAnimationCancel(View view) {
109 | ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
110 | }
111 |
112 | public void onAnimationEnd(View view) {
113 | ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
114 | view.setVisibility(View.GONE);
115 | }
116 | }).start();
117 | } else {
118 | Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out);
119 | anim.setInterpolator(INTERPOLATOR);
120 | anim.setDuration(200L);
121 | anim.setAnimationListener(new Animation.AnimationListener() {
122 | public void onAnimationStart(Animation animation) {
123 | ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
124 | }
125 |
126 | public void onAnimationEnd(Animation animation) {
127 | ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
128 | button.setVisibility(View.GONE);
129 | }
130 |
131 | @Override
132 | public void onAnimationRepeat(final Animation animation) {
133 | }
134 | });
135 | button.startAnimation(anim);
136 | }
137 | }
138 |
139 | // Same animation that FloatingActionButton.Behavior
140 | // uses to show the FAB when the AppBarLayout enters
141 | private void animateIn(FloatingActionButton button) {
142 | button.setVisibility(View.VISIBLE);
143 | if (Build.VERSION.SDK_INT >= 14) {
144 | ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
145 | .setInterpolator(INTERPOLATOR).withLayer().setListener(null)
146 | .start();
147 | } else {
148 | Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in);
149 | anim.setDuration(200L);
150 | anim.setInterpolator(INTERPOLATOR);
151 | button.startAnimation(anim);
152 | }
153 | }
154 | }
155 |
156 | ```
157 |
158 | ## Shared Element Transition
159 |
160 | Shared Element Transition是Android 5.0引入的动画效果,可以在两个Activity中实现共享元素变换动画,
161 | 在我的这个例子中,就是实现点击Item后,图片可以慢慢放大和移位的效果。
162 |
163 | ### 设置transitionName
164 |
165 | 首先,我们需要选定变换开始和结束的元素,给它们设置一个相同的`transitionName`,
166 | 我这个例子中,开始变换的元素是列表中的图片
167 |
168 | ```xml
169 |
175 | ```
176 |
177 | 开始变换的元素是图书明细界面中的图片
178 |
179 | ```xml
180 |
189 | ```
190 |
191 | `transition_book_img`就是一个普通的string,只要值是唯一的就可以了。
192 |
193 | ### 使用Shared Element Transition
194 |
195 | 在Activity切换的时候,使用Shared Element Transition
196 |
197 | ```java
198 | Book book = mAdapter.getBook(position);
199 | Intent intent = new Intent(getActivity(), BookDetailActivity.class);
200 | intent.putExtra("book", book);
201 | ActivityOptionsCompat options =
202 | ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
203 | view.findViewById(R.id.ivBook), getString(R.string.transition_book_img));
204 |
205 | ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
206 | ```
207 |
208 | 使用`ActivityOptionsCompat`的`makeSceneTransitionAnimation`方法创建动画,
209 | 需要指定一个View作为起始变换的View,以及变换的transitionName,
210 | 我这里使用的是`ivBook`作为起始View,`transition_book_img`作为变换的transitionName,与XML中的设定一致。
211 |
212 | 这样就OK了,ImageView的位移和大小变化,Android会自动帮你搞定。
213 | 当然我们也可以自定义变换动画,后续再研究~~~
214 |
215 | > 这个效果只支持Android 5.0以上
--------------------------------------------------------------------------------
/Android基础/网络编程day2笔记.md:
--------------------------------------------------------------------------------
1 | #HttpClient
2 | ###发送get请求
3 | * 创建一个客户端对象
4 |
5 | ```java
6 | HttpClient client = new DefaultHttpClient();
7 | ```
8 | * 创建一个get请求对象
9 |
10 | ```java
11 | HttpGet hg = new HttpGet(path);
12 | ```
13 | * 发送get请求,建立连接,返回响应头对象
14 |
15 | ```java
16 | HttpResponse hr = hc.execute(hg);
17 | ```
18 | * 获取状态行对象,获取状态码,如果为200则说明请求成功
19 |
20 | ```java
21 | if(hr.getStatusLine().getStatusCode() == 200){
22 | //拿到服务器返回的输入流
23 | InputStream is = hr.getEntity().getContent();
24 | String text = Utils.getTextFromStream(is);
25 | }
26 | ```
27 | ###发送post请求
28 |
29 | ```java
30 | //创建一个客户端对象
31 | HttpClient client = new DefaultHttpClient();
32 | //创建一个post请求对象
33 | HttpPost hp = new HttpPost(path);
34 | ```
35 | * 往post对象里放入要提交给服务器的数据
36 |
37 | ```java
38 | //要提交的数据以键值对的形式存在BasicNameValuePair对象中
39 | List parameters = new ArrayList();
40 | BasicNameValuePair bnvp = new BasicNameValuePair("name", name);
41 | BasicNameValuePair bnvp2 = new BasicNameValuePair("pass", pass);
42 | parameters.add(bnvp);
43 | parameters.add(bnvp2);
44 | //创建实体对象,指定进行URL编码的码表
45 | UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");
46 | //为post请求设置实体
47 | hp.setEntity(entity);
48 | ```
49 | ---
50 | #异步HttpClient框架
51 | ###发送get请求
52 |
53 | ```java
54 | //创建异步的httpclient对象
55 | AsyncHttpClient ahc = new AsyncHttpClient();
56 | //发送get请求
57 | ahc.get(path, new MyHandler());
58 | ```
59 | * 注意AsyncHttpResponseHandler两个方法的调用时机
60 |
61 | ```java
62 | class MyHandler extends AsyncHttpResponseHandler{
63 |
64 | //http请求成功,返回码为200,系统回调此方法
65 | @Override
66 | public void onSuccess(int statusCode, Header[] headers,
67 | //responseBody的内容就是服务器返回的数据
68 | byte[] responseBody) {
69 | Toast.makeText(MainActivity.this, new String(responseBody), 0).show();
70 |
71 | }
72 |
73 | //http请求失败,返回码不为200,系统回调此方法
74 | @Override
75 | public void onFailure(int statusCode, Header[] headers,
76 | byte[] responseBody, Throwable error) {
77 | Toast.makeText(MainActivity.this, "返回码不为200", 0).show();
78 |
79 | }
80 |
81 | }
82 | ```
83 | ###发送post请求
84 |
85 | * 使用RequestParams对象封装要携带的数据
86 |
87 | ```java
88 | //创建异步httpclient对象
89 | AsyncHttpClient ahc = new AsyncHttpClient();
90 | //创建RequestParams封装要携带的数据
91 | RequestParams rp = new RequestParams();
92 | rp.add("name", name);
93 | rp.add("pass", pass);
94 | //发送post请求
95 | ahc.post(path, rp, new MyHandler());
96 | ```
97 | ---
98 | #多线程下载
99 | >原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源
100 |
101 | ###确定每条线程下载多少数据
102 | * 发送http请求至下载地址
103 |
104 | ```java
105 | String path = "http://192.168.1.102:8080/editplus.exe";
106 | URL url = new URL(path);
107 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
108 | conn.setReadTimeout(5000);
109 | conn.setConnectTimeout(5000);
110 | conn.setRequestMethod("GET");
111 | ```
112 | * 获取文件总长度,然后创建长度一致的临时文件
113 |
114 | ```java
115 | if(conn.getResponseCode() == 200){
116 | //获得服务器流中数据的长度
117 | int length = conn.getContentLength();
118 | //创建一个临时文件存储下载的数据
119 | RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
120 | //设置临时文件的大小
121 | raf.setLength(length);
122 | raf.close();
123 | }
124 | ```
125 |
126 | * 确定线程下载多少数据
127 |
128 | ```java
129 | //计算每个线程下载多少数据
130 | int blockSize = length / THREAD_COUNT;
131 | ```
132 | ###计算每条线程下载数据的开始位置和结束位置
133 |
134 | ```java
135 | for(int id = 1; id <= 3; id++){
136 | //计算每个线程下载数据的开始位置和结束位置
137 | int startIndex = (id - 1) * blockSize;
138 | int endIndex = id * blockSize - 1;
139 | if(id == THREAD_COUNT){
140 | endIndex = length;
141 | }
142 |
143 | //开启线程,按照计算出来的开始结束位置开始下载数据
144 | new DownLoadThread(startIndex, endIndex, id).start();
145 | }
146 | ```
147 | ###再次发送请求至下载地址,请求开始位置至结束位置的数据
148 |
149 | ```java
150 | String path = "http://192.168.1.102:8080/editplus.exe";
151 |
152 | URL url = new URL(path);
153 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
154 | conn.setReadTimeout(5000);
155 | conn.setConnectTimeout(5000);
156 | conn.setRequestMethod("GET");
157 |
158 | //向服务器请求部分数据
159 | conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
160 | conn.connect();
161 | ```
162 | * 下载请求到的数据,存放至临时文件中
163 |
164 | ```java
165 | //请求部分数据,相应码是206
166 | if(conn.getResponseCode() == 206){
167 | InputStream is = conn.getInputStream();
168 | RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
169 | //指定从哪个位置开始存放数据
170 | raf.seek(startIndex);
171 | byte[] b = new byte[1024];
172 | int len;
173 | while((len = is.read(b)) != -1){
174 | raf.write(b, 0, len);
175 | }
176 | raf.close();
177 | }
178 | ```
179 | ---
180 | #带断点续传的多线程下载
181 | * 定义一个int变量记录每条线程下载的数据总长度,然后加上该线程的下载开始位置,得到的结果就是下次下载时,该线程的开始位置,把得到的结果存入缓存文件
182 |
183 | ```java
184 | //用来记录当前线程总的下载长度
185 | int total = 0;
186 | while((len = is.read(b)) != -1){
187 | raf.write(b, 0, len);
188 | total += len;
189 | //每次下载都把新的下载位置写入缓存文本文件
190 | RandomAccessFile raf2 = new RandomAccessFile(threadId + ".txt", "rwd");
191 | raf2.write((startIndex + total + "").getBytes());
192 | raf2.close();
193 | }
194 | ```
195 | * 下次下载开始时,先读取缓存文件中的值,得到的值就是该线程新的开始位置
196 |
197 | ```java
198 | FileInputStream fis = new FileInputStream(file);
199 | BufferedReader br = new BufferedReader(new InputStreamReader(fis));
200 | String text = br.readLine();
201 | int newStartIndex = Integer.parseInt(text);
202 | //把读到的值作为新的开始位置
203 | startIndex = newStartIndex;
204 | fis.close();
205 | ```
206 | * 三条线程都下载完毕之后,删除缓存文件
207 |
208 | ```java
209 | RUNNING_THREAD--;
210 | synchronized (path){
211 | if(RUNNING_THREAD == 0){
212 | for(int i = 0; i <= ThreadCount; i++){
213 | File f = new File(i + ".txt");
214 | f.delete();
215 | }
216 | finishedThread = 0 ;
217 | }
218 | }
219 |
220 | ```
221 | ---
222 | #手机版的断点续传多线程下载器
223 | * 把刚才的代码直接粘贴过来就能用,记得在访问文件时的路径要改成Android的目录,添加访问网络和外部存储的路径
224 |
225 | ###用进度条显示下载进度
226 | - 设置style
227 |
228 | ```xml
229 |
233 | ```
234 |
235 | * 拿到下载文件总长度时,设置进度条的最大值
236 |
237 | ```java
238 | //设置进度条的最大值
239 | pb.setMax(length);
240 | ```
241 | * 进度条需要显示三条线程的整体下载进度,所以三条线程每下载一次,就要把新下载的长度加入进度条
242 | * 定义一个int全局变量,记录三条线程的总下载长度
243 |
244 | ```java
245 | int progress;
246 | ```
247 | * 刷新进度条
248 |
249 | ```java
250 | while((len = is.read(b)) != -1){
251 | raf.write(b, 0, len);
252 |
253 |
254 | //把当前线程本次下载的长度加到进度条里
255 | progress += len;
256 | pb.setProgress(progress);
257 | }
258 | ```
259 | * 每次断点下载时,从新的开始位置开始下载,进度条也要从新的位置开始显示,在读取缓存文件获取新的下载开始位置时,也要处理进度条进度
260 |
261 | ```java
262 | FileInputStream fis = new FileInputStream(file);
263 | BufferedReader br = new BufferedReader(new InputStreamReader(fis));
264 | String text = br.readLine();
265 | int newStartIndex = Integer.parseInt(text);
266 |
267 | //新开始位置减去原本的开始位置,得到已经下载的数据长度
268 | int alreadyDownload = newStartIndex - startIndex;
269 | //把已经下载的长度设置入进度条
270 | progress += alreadyDownload;
271 | ```
272 | ###添加文本框显示百分比进度
273 |
274 | ```java
275 | tv.setText((long)progress * 100 / pb.getMax() + "%");
276 | ```
277 | ---
278 |
279 | #xUtils的HttpUtils的使用
280 | >HttpUtils本身就支持多线程断点续传,使用起来非常的方便
281 |
282 | * 创建HttpUtils对象
283 |
284 | ```java
285 | HttpUtils http = new HttpUtils();
286 | ```
287 | * 下载文件
288 |
289 | ```java
290 | http.download(url, //下载请求的网址
291 | target, //下载的数据保存路径和文件名
292 | true, //是否开启断点续传
293 | true, //如果服务器响应头中包含了文件名,那么下载完毕后自动重命名
294 | new RequestCallBack() {//侦听下载状态
295 |
296 | //下载成功此方法调用
297 | @Override
298 | public void onSuccess(ResponseInfo arg0) {
299 | tv.setText("下载成功" + arg0.result.getPath());
300 | }
301 |
302 | //下载失败此方法调用,比如文件已经下载、没有网络权限、文件访问不到,方法传入一个字符串参数告知失败原因
303 | @Override
304 | public void onFailure(HttpException arg0, String arg1) {
305 | tv.setText("下载失败" + arg1);
306 | }
307 |
308 | //在下载过程中不断的调用,用于刷新进度条
309 | @Override
310 | public void onLoading(long total, long current, boolean isUploading) {
311 | super.onLoading(total, current, isUploading);
312 | //设置进度条总长度
313 | pb.setMax((int) total);
314 | //设置进度条当前进度
315 | pb.setProgress((int) current);
316 | tv_progress.setText(current * 100 / total + "%");
317 | }
318 | });
319 | ```
320 |
321 | ---
322 | - 邮箱 :elisabethzhen@163.com
323 | - Good Luck!
324 |
--------------------------------------------------------------------------------
/Android基础/网络编程day1笔记.md:
--------------------------------------------------------------------------------
1 | #网络图片查看器
2 | * 确定图片的网址
3 | * 发送http请求
4 |
5 | ```java
6 | URL url = new URL(address);
7 | //获取连接对象,并没有建立连接
8 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
9 | //设置连接和读取超时
10 | conn.setConnectTimeout(5000);
11 | conn.setReadTimeout(5000);
12 | //设置请求方法,注意必须大写
13 | conn.setRequestMethod("GET");
14 | //建立连接,发送get请求
15 | //conn.connect();
16 | //getResponseCode()本来就会去建立连接,所以conn.connect();可去
17 | //然后获取响应吗,200说明请求成功
18 | if (conn.getResponseCode() == 200) {
19 | .......
20 | }
21 | ```
22 | * 服务器的图片是以流的形式返回给浏览器的
23 |
24 | ```java
25 | //拿到服务器返回的输入流
26 | InputStream is = conn.getInputStream();
27 | //把流里的数据读取出来,并构造成图片
28 | Bitmap bm = BitmapFactory.decodeStream(is);
29 | ```
30 | * 把图片设置为ImageView的显示内容
31 |
32 | ```java
33 | ImageView iv = (ImageView) findViewById(R.id.iv);
34 | iv.setImageBitmap(bm);//只能在主线程中执行
35 | ```
36 | * 添加权限
37 |
38 | ```xml
39 |
40 | ```
41 |
42 | ###主线程不能被阻塞
43 | * 在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差
44 | * 主线程阻塞时间过长,系统会抛出ANR异常
45 | * ANR:Application Not Response;应用无响应
46 | * **任何耗时操作都不可以写在主线程**
47 | * 因为网络交互属于耗时操作,如果网速很慢,代码会阻塞,所以网络交互的代码不能运行在主线程
48 |
49 | ###只有主线程能刷新ui
50 | * 刷新ui的代码只能运行在主线程,运行在子线程是没有任何效果的
51 | * 如果需要在子线程中刷新ui,使用**消息队列机制**
52 |
53 | ###Handler用途
54 | 1. 当主线程需要子线程完成之后再执行的操作,并且这些操作不能在子线程中完成,例如修改UI子类的
55 |
56 | #####消息队列
57 | * **Looper**(消息轮询器)一旦发现**Message Queue**中有消息,就会把消息取出,然后把消息扔给**Handler**(消息处理器)对象,Handler会调用自己的handleMessage方法来处理这条消息
58 | * handleMessage方法运行在主线程
59 | * 主线程创建时,消息队列和轮询器对象就会被创建,但是消息处理器对象,需要使用时,自行创建
60 | * 总结:只要消息队列有消息,handleMessenger方法就会被调用
61 |
62 | ```java
63 | //消息队列
64 | static Handler handler = new Handler(){
65 | //主线程中有一个消息轮询器looper,不断检测消息队列中是否有新消息,如果发现有新消息,自动调用此方法,注意此方法是在主线程中运行的
66 | public void handleMessage(android.os.Message msg) {
67 |
68 | }
69 | };
70 | ```
71 | * 在子线程使用handler往消息队列里发消息
72 |
73 | ```java
74 | //创建消息对象
75 | Message msg = new Message();// Message msg = handler.obtainMessage();
76 | //消息的obj属性可以赋值任何对象,通过这个属性可以携带数据
77 | msg.obj = bm;
78 | //what属性相当于一个标签,用于区分出不同的消息,从而运行不能的代码
79 | msg.what = 1;
80 | //发送消息
81 | handler.sendMessage(msg);
82 | ```
83 | * 通过switch语句区分不同的消息
84 |
85 | ```java
86 | public void handleMessage(android.os.Message msg) {
87 | switch (msg.what) {
88 | //如果是1,说明属于请求成功的消息
89 | case 1:
90 | ImageView iv = (ImageView) findViewById(R.id.iv);
91 | Bitmap bm = (Bitmap) msg.obj;
92 | iv.setImageBitmap(bm);
93 | break;
94 | case 2:
95 | Toast.makeText(MainActivity.this, "请求失败", 0).show();
96 | break;
97 | }
98 | }
99 | ```
100 | ###加入缓存图片的功能
101 | * 把服务器返回的流里的数据读取出来,然后通过文件输入流写至本地文件
102 |
103 | ```java
104 | //1.拿到服务器返回的输入流
105 | InputStream is = conn.getInputStream();
106 | //2.把流里的数据读取出来,并构造成图片
107 | //File file = new File(getCacheDir(), getFileName(path));
108 | FileOutputStream fos = new FileOutputStream(file);
109 | byte[] b = new byte[1024];//1024,每次读取1K
110 | int len = 0;
111 | while((len = is.read(b)) != -1){
112 | fos.write(b, 0, len);
113 | }
114 | ```
115 | * 创建bitmap对象的代码改成
116 |
117 | ```java
118 | Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
119 | ```
120 | * 每次发送请求前检测一下在缓存中是否存在同名图片,如果存在,则读取缓存
121 |
122 | ```java
123 | //1.确定网站
124 | final String path = "";
125 | final File file = new File(getCacheDir(), getFileName(path));
126 | //2.判断,缓存中是否存在该文件
127 | if (file.exists()) {
128 | //如果缓存存在,从缓存读取图片
129 | Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
130 | iv.setImageBitmap(bm);
131 | }else{
132 | //如果缓存不存在,从网络下载
133 | ......
134 | }
135 | ```
136 | * 获取文件名
137 |
138 | ```java
139 | public String getFileName(String String){
140 | int index = path.lastIndexOf("/");
141 | return path.substring(index + 1);
142 | }
143 | ```
144 | ---
145 | #获取开源代码的网站
146 | * code.google.com
147 | * github.com
148 | * 在github搜索smart-image-view
149 | * 下载开源项目smart-image-view
150 | * 使用自定义组件时,标签名字要写包名
151 |
152 | ```java
153 |
154 | ```
155 | * SmartImageView的使用
156 |
157 | ```java
158 | SmartImageView siv = (SmartImageView) findViewById(R.id.siv);
159 | siv.setImageUrl("http://192.168.1.102:8080/dd.jpg");
160 | ```
161 | ---
162 | #Html源文件查看器
163 | * 发送GET请求
164 |
165 | ```java
166 | URL url = new URL(path);
167 | //获取连接对象
168 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
169 | //设置连接属性
170 | conn.setRequestMethod("GET");
171 | conn.setConnectTimeout(5000);
172 | conn.setReadTimeout(5000);
173 | //建立连接,获取响应吗
174 | if(conn.getResponseCode() == 200){
175 |
176 | }
177 | ```
178 | * 获取服务器返回的流,从流中把html源码读取出来
179 |
180 | ```java
181 | byte[] b = new byte[1024];
182 | int len = 0;
183 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
184 | while((len = is.read(b)) != -1){
185 | //把读到的字节先写入字节数组输出流中存起来
186 | bos.write(b, 0, len);
187 | }
188 | //把字节数组输出流中的内容转换成字符串
189 | //默认使用utf-8
190 | text = new String(bos.toByteArray());
191 | ```
192 | ###乱码的处理
193 | * 乱码的出现是因为服务器和客户端码表不一致导致
194 |
195 | ```java
196 | //手动指定码表
197 | text = new String(bos.toByteArray(), "gbk");
198 | ```
199 | ---
200 | ###获取xml文件并且解析
201 |
202 | ```java
203 | List newsList;
204 |
205 | private void getNewsInfo(){
206 | Thread t = new Thread(){
207 | @Override
208 | public void run(){
209 | String path = "....news.xml";
210 | try{
211 | URL url = new URl(path);
212 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
213 | conn.setRequestMethod("GET");
214 | conn.setReadTimeout(5000);
215 | conn.setConnectTimeout(5000);
216 | //发送http GET请求,获取相应码
217 | if (conn.getResponseCode() == 200 ){
218 | InputStream is = conn.getInputStream();
219 | //使用pull解析器,解析这个流
220 | parseNewsXml(is);
221 | }
222 | } catch (Exception e) {
223 | e.printStackTrace();
224 | }
225 | }
226 | };
227 | t.start();
228 | }
229 |
230 | private void parseNewsXml(InputStream is){
231 | XmlPullParser xp = Xml.newPullParser();
232 | try{
233 | xp.setInput(is, "utf-8");
234 | //对节点的事件类型进行判断,就可以知道当前节点是什么节点
235 | int type = xp.getEventType();
236 | News news = null;
237 | while(type != XmlPullParser.END_DOCUMENT){
238 | switch (type) {
239 | case XmlPullParser.START_TAG:
240 | if ("newslist".equals(xp.getName())) {
241 | newsList = new ArrayList;
242 | }else if ("news".equals(xp.getName())) {
243 | news = new News();
244 | }else if ("title".equals(xp.getName())) {
245 | String title = xp.nextText();
246 | news.setTitle(title);
247 | }
248 | break;
249 | case XmlPullParser.END_TAG:
250 | if ("news".equals(xp.getName())) {
251 | newsList.add(news);
252 | }
253 | break;
254 | }
255 |
256 | //解析完当前节点后,把指针移动到下一个节点,并返回它的事件类型
257 | type = xp.next();
258 | }
259 |
260 |
261 | } catch (Exception e){
262 | e.printStackTrace();
263 | }
264 | }
265 | ```
266 |
267 | ---
268 | #提交数据
269 | ###GET方式提交数据
270 | * get方式提交的数据是直接拼接在url的末尾
271 |
272 | ```java
273 | final String path = "http://localhost/Web/servlet/CheckLogin?name=" + name + "&pass=" + pass;
274 | ```
275 | * 发送get请求,代码和之前一样
276 |
277 | ```java
278 | URL url = new URL(path);
279 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
280 | conn.setRequestMethod("GET");
281 | conn.setReadTimeout(5000);
282 | conn.setConnectTimeout(5000);
283 | if(conn.getResponseCode() == 200){
284 |
285 | }
286 | ```
287 | * 浏览器在发送请求携带数据时会对数据进行URL编码,我们写代码时也需要为中文进行URL编码
288 |
289 | ```java
290 | String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;
291 | ```
292 | ###POST方式提交数据
293 | * post提交数据是用流写给服务器的
294 | * 协议头中多了两个属性
295 | * Content-Type: application/x-www-form-urlencoded,描述提交的数据的mimetype
296 | * Content-Length: 32,描述提交的数据的长度
297 |
298 | * 发送post请求
299 |
300 | ```java
301 | URL url = new URL(path);
302 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
303 | conn.setRequestMethod("POST");
304 | conn.setReadTimeout(5000);
305 | conn.setConnectTimeout(5000);
306 |
307 | //给请求头添加post多出来的两个属性
308 | String data = "name=" + URLEncoder.encode(name) + "&pass=" + pass;
309 | conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
310 | conn.setRequestProperty("Content-Length", data.length() + "");
311 |
312 | conn.setDoOutput(true);
313 | //拿到输出流
314 | OutputStream os = conn.getOutputStream();
315 | os.write(data.getBytes());
316 | if(conn.getResponseCode() == 200){
317 |
318 | }
319 |
320 | ```
321 | * 设置允许打开post请求的流
322 |
323 | ```java
324 | conn.setDoOutput(true);
325 | ```
326 | * 获取连接对象的输出流,往流里写要提交给服务器的数据
327 |
328 | ```java
329 | OutputStream os = conn.getOutputStream();
330 | os.write(data.getBytes());
331 | ```
332 | ---
333 | - 邮箱 :elisabethzhen@163.com
334 | - Good Luck!
335 |
--------------------------------------------------------------------------------
/Android基础/数据存储和界面展现day1笔记.md:
--------------------------------------------------------------------------------
1 | #常见布局
2 | ###相对布局
3 | #####RelativeLayout
4 | * 组件默认左对齐、顶部对齐
5 | * 设置组件在指定组件的右边
6 |
7 | android:layout_toRightOf="@id/tv1"
8 |
9 | * 设置在指定组件的下边
10 |
11 | android:layout_below="@id/tv1"
12 |
13 | * 设置右对齐父元素
14 |
15 | android:layout_alignParentRight="true"
16 |
17 | * 设置与指定组件右对齐
18 |
19 | android:layout_alignRight="@id/tv1"
20 |
21 | ###线性布局
22 | #####LinearLayout
23 | * 指定各个节点的排列方向
24 |
25 | android:orientation="horizontal"
26 |
27 | * 设置右对齐
28 |
29 | android:layout_gravity="right"
30 |
31 | * 当竖直布局时,只能左右对齐和水平居中,顶部底部对齐竖直居中无效
32 | * 当水平布局时,只能顶部底部对齐和竖直居中
33 | * 使用match_parent时注意不要把其他组件顶出去
34 | * 线性布局非常重要的一个属性:权重
35 |
36 | android:layout_weight="1"
37 |
38 | * 权重设置的是按比例分配剩余的空间
39 |
40 | ###帧布局
41 | #####FrameLayout
42 | * 默认组件都是左对齐和顶部对齐,每个组件相当于一个div
43 | * 可以更改对齐方式
44 |
45 | android:layout_gravity="bottom"
46 |
47 | * 不能相对于其他组件布局
48 |
49 | ###表格布局
50 | #####TableLayout
51 | * 每个节点是一行,它的每个子节点是一列
52 | * 表格布局中的节点可以不设置宽高,因为设置了也无效
53 | * 根节点的子节点宽为匹配父元素,高为包裹内容
54 | * 节点的子节点宽为包裹内容,高为包裹内容
55 | * 以上默认属性无法修改
56 |
57 | * 根节点中可以设置以下属性,表示让第1列拉伸填满屏幕宽度的剩余空间
58 |
59 | android:stretchColumns="1"
60 |
61 | ###绝对布局
62 | #####AbsoluteLayout
63 | * 直接指定组件的x、y坐标
64 |
65 | android:layout_x="144dp"
66 | android:layout_y="154dp"
67 |
68 | ---
69 | #logcat
70 | * 日志信息总共分为5个等级
71 | * verbose
72 | * debug
73 | * info
74 | * warn
75 | * error
76 | * 定义过滤器方便查看
77 | * System.out.print输出的日志级别是info,tag是System.out
78 | * Android提供的日志输出api
79 |
80 | Log.v(TAG, "加油吧,童鞋们");
81 | Log.d(TAG, "加油吧,童鞋们");
82 | Log.i(TAG, "加油吧,童鞋们");
83 | Log.w(TAG, "加油吧,童鞋们");
84 | Log.e(TAG, "加油吧,童鞋们");
85 |
86 | ----
87 | #文件读写操作
88 | * Ram内存:运行内存,相当于电脑的内存
89 | * Rom内存:内部存储空间,相当于电脑的硬盘
90 | * sd卡:外部存储空间,相当于电脑的移动硬盘
91 |
92 | ###在内部存储空间中读写文件
93 | >小案例:用户输入账号密码,勾选“记住账号密码”,点击登录按钮,登录的同时持久化保存账号和密码
94 |
95 | #####1. 定义布局
96 |
97 | #####2. 完成按钮的点击事件
98 | * 弹土司提示用户登录成功
99 |
100 | ```java
101 | Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
102 | ```
103 | #####3. 拿到用户输入的数据
104 | * 判断用户是否勾选保存账号密码
105 |
106 | ```java
107 | CheckBox cb = (CheckBox) findViewById(R.id.cb);
108 | if(cb.isChecked()){
109 |
110 | }
111 | ```
112 | #####4. 开启io流把文件写入内部存储
113 | * 直接开启文件输出流写数据
114 |
115 | ```java
116 | //持久化保存数据
117 | File file = new File("data/data/com.itheima.rwinrom/info.txt");
118 | FileOutputStream fos = new FileOutputStream(file);
119 | fos.write((name + "##" + pass).getBytes());
120 | fos.close();
121 | ```
122 | * 读取数据前先检测文件是否存在
123 |
124 | ```java
125 | if(file.exists())
126 | ```
127 | * 读取保存的数据,也是直接开文件输入流读取
128 |
129 | ```java
130 | File file = new File("data/data/com.itheima.rwinrom/info.txt");
131 | FileInputStream fis = new FileInputStream(file);
132 | //把字节流转换成字符流
133 | BufferedReader br = new BufferedReader(new InputStreamReader(fis));
134 | String text = br.readLine();
135 | String[] s = text.split("##");
136 | ```
137 | * 读取到数据之后,回显至输入框
138 |
139 | ```java
140 | et_name.setText(s[0]);
141 | et_pass.setText(s[1]);
142 | ```
143 | * 应用只能在自己的包名目录下创建文件,不能到别人家去创建
144 |
145 | ###直接复制项目
146 | * 需要改动的地方:
147 | * 项目名字
148 | * 应用包名
149 | * R文件重新导包
150 |
151 | ###使用路径api读写文件
152 | * getFilesDir()得到的file对象的路径是data/data/com.itheima.rwinrom2/files
153 | * 存放在这个路径下的文件,只要你不删,它就一直在
154 | * getCacheDir()得到的file对象的路径是data/data/com.itheima.rwinrom2/cache
155 | * 存放在这个路径下的文件,当内存不足时,有可能被删除
156 |
157 | * 系统管理应用界面的清除缓存,会清除cache文件夹下的东西,清除数据,会清除整个包名目录下的东西
158 |
159 | -----
160 | #在外部存储读写数据
161 | ###sd卡的路径
162 | * sdcard:2.3之前的sd卡路径
163 | * mnt/sdcard:4.3之前的sd卡路径
164 | * storage/sdcard:4.3之后的sd卡路径
165 |
166 | * 最简单的打开sd卡的方式
167 |
168 | ```java
169 | File file = new File("sdcard/info.txt");
170 | ```
171 | * 写sd卡需要权限
172 |
173 |
174 |
175 | * 读sd卡,在4.0之前不需要权限,4.0之后可以设置为需要
176 |
177 |
178 |
179 | * 使用api获得sd卡的真实路径,部分手机品牌会更改sd卡的路径
180 |
181 | Environment.getExternalStorageDirectory()
182 |
183 | * 判断sd卡是否准备就绪
184 |
185 | ```java
186 | if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
187 | ```
188 | -----
189 | #查看源代码查找获取sd卡剩余容量的代码
190 | * 导入Settings项目
191 | * 查找“可用空间”得到
192 |
193 | "可用空间"
194 |
195 | * 查找"memory_available",得到
196 |
197 |
201 |
202 | * 查找"memory_sd_avail",得到
203 |
204 | ```java
205 | //这个字符串就是sd卡剩余容量
206 | formatSize(availableBlocks * blockSize) + readOnly
207 | //这两个参数相乘,得到sd卡以字节为单位的剩余容量
208 | availableBlocks * blockSize
209 | ```
210 | * 存储设备会被分为若干个区块,每个区块有固定的大小
211 | * **区块大小** * **区块数量** 等于 **存储设备的总大小**
212 |
213 | ###获取sd卡剩余容量
214 | ```java
215 | protected void onCreate(Bundle savedInstanceState) {
216 | super.onCreate(savedInstanceState);
217 | setContentView(R.layout.activity_main);
218 |
219 | File path = Environment.getExternalStorageDirectory();
220 | StatFs stat = new StatFs(path.getPath());
221 | long blockSize;
222 | long totalBlocks;
223 | long availableBlocks;
224 |
225 | //获取当前系统版本的等级
226 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
227 | blockSize = stat.getBlockSizeLong();
228 | totalBlocks = stat.getBlockCountLong();
229 | availableBlocks = stat.getAvailableBlocksLong();
230 | }
231 | else{
232 | blockSize = stat.getBlockSize();
233 | totalBlocks = stat.getBlockCount();
234 | availableBlocks = stat.getAvailableBlocks();
235 | }
236 |
237 | TextView tv = (TextView) findViewById(R.id.tv);
238 | tv.setText(formatSize(availableBlocks * blockSize));//剩余容量
239 | }
240 |
241 | private String formatSize(long size) {
242 | return Formatter.formatFileSize(this, size);
243 | }
244 | ```
245 | -------
246 | #Linux文件的访问权限
247 | * 在Android中,每一个应用是一个独立的用户
248 | * 使用10个字母表示
249 | * drwxrwxrwx
250 | * 第1位:d表示文件夹,-表示文件
251 | * 第2-4位:rwx,表示这个文件的拥有者用户(owner)对该文件的权限
252 | * r:读
253 | * w:写
254 | * x:执行
255 | * 第5-7位:rwx,表示跟文件拥有者用户同组的用户(grouper)对该文件的权限
256 | * 第8-10位:rwx,表示其他用户组的用户(other)对该文件的权限
257 |
258 | ----
259 | #openFileOutput的四种模式
260 | * MODE_PRIVATE:-rw-rw----
261 | * MODE_APPEND:-rw-rw----
262 | * MODE_WORLD_WRITEABLE:-rw-rw--w-
263 | * MODE_WORLD_READABLE:-rw-rw-r--
264 |
265 | ----
266 | #SharedPreference
267 | >用SharedPreference存储账号密码
268 |
269 | * 往SharedPreference里写数据
270 |
271 | ```java
272 | //拿到一个SharedPreference对象
273 | SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
274 | //拿到编辑器
275 | Editor ed = sp.edit();
276 | //写数据
277 | ed.putBoolean("name", name);
278 | ed.commit();
279 | ```
280 | * 从SharedPreference里取数据
281 |
282 | ```java
283 | SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
284 | //从SharedPreference里取数据
285 | String name = sp.getBoolean("name", "");
286 | ```
287 | ---
288 | #生成XML文件备份短信
289 |
290 | * 创建几个虚拟的短信对象,存在list中
291 | * 备份数据通常都是备份至sd卡
292 | ###使用StringBuffer拼接字符串
293 | * 把整个xml文件所有节点append到sb对象里
294 |
295 | ```java
296 | sb.append("");
297 | //添加smss的开始节点
298 | sb.append("");
299 | .......
300 | ```
301 | * 把sb写到输出流中
302 |
303 | ```java
304 | fos.write(sb.toString().getBytes());
305 | ```
306 | ###使用XMl序列化器生成xml文件
307 | * 得到xml序列化器对象
308 |
309 | ```java
310 | XmlSerializer xs = Xml.newSerializer();
311 | ```
312 | * 给序列化器设置输出流
313 |
314 | ```java
315 | File file = new File(Environment.getExternalStorageDirectory(), "backupsms.xml");
316 | FileOutputStream fos = new FileOutputStream(file);
317 | //给序列化器指定好输出流
318 | xs.setOutput(fos, "utf-8");//enconding:指定用什么编码生成xml文件
319 | ```
320 | * 开始生成xml文件
321 |
322 | ```java
323 | //enconding:指定头结点中的enconding属性的值
324 | xs.startDocument("utf-8", true);
325 | xs.startTag(null, "smss");
326 | ......
327 | xs.endTag(null, "smss");
328 | //告诉序列化器,文件生成完毕
329 | xs.endDocument();
330 | ```
331 | ---
332 | #pull解析xml文件
333 | * 先自己写一个xml文件,存一些天气信息
334 |
335 | ###拿到xml文件
336 |
337 | ```java
338 | InputStream is = getClassLoader().getResourceAsStream("weather.xml");
339 | ```
340 | ###拿到pull解析器
341 |
342 | ```java
343 | XmlPullParser xp = Xml.newPullParser();
344 | ```
345 | ###开始解析
346 | * 拿到指针所在当前节点的事件类型
347 |
348 | ```java
349 | int type = xp.getEventType();
350 | ```
351 | * 事件类型主要有五种
352 | * START_DOCUMENT:xml头的事件类型
353 | * END_DOCUMENT:xml尾的事件类型
354 | * START_TAG:开始节点的事件类型
355 | * END_TAG:结束节点的事件类型
356 | * TEXT:文本节点的事件类型
357 |
358 | * 如果获取到的事件类型不是END_DOCUMENT,就说明解析还没有完成,如果是,解析完成,while循环结束
359 |
360 | ```java
361 | while(type != XmlPullParser.END_DOCUMENT)
362 | ```
363 | * 当我们解析到不同节点时,需要进行不同的操作,所以判断一下当前节点的name
364 | * 当解析到weather的开始节点时,new出list
365 | * 当解析到city的开始节点时,创建city对象,创建对象是为了更方便的保存即将解析到的文本
366 | * 当解析到name开始节点时,获取下一个节点的文本内容,temp、pm也是一样
367 |
368 | ```java
369 | case XmlPullParser.START_TAG:
370 | //获取当前节点的名字
371 | if("weather".equals(xp.getName())){
372 | citys = new ArrayList();
373 | }
374 | else if("city".equals(xp.getName())){
375 | city = new City();
376 | }
377 | else if("name".equals(xp.getName())){
378 | //获取当前节点的下一个节点的文本
379 | String name = xp.nextText();
380 | city.setName(name);
381 | }
382 | else if("temp".equals(xp.getName())){
383 | String temp = xp.nextText();
384 | city.setTemp(temp);
385 | }
386 | else if("pm".equals(xp.getName())){
387 | String pm = xp.nextText();
388 | city.setPm(pm);
389 | }
390 | break;
391 | ```
392 | * 当解析到city的结束节点时,说明city的三个子节点已经全部解析完了,把city对象添加至list
393 |
394 | ```java
395 | case XmlPullParser.END_TAG:
396 | if("city".equals(xp.getName())){
397 | citys.add(city);
398 | }
399 | ```
400 | >换行符也是文本节点
401 |
402 | ---
403 | - 邮箱 :elisabethzhen@163.com
404 | - Good Luck!
--------------------------------------------------------------------------------
/Android进阶/Github开源项目源码解析/Volley完全解析.md:
--------------------------------------------------------------------------------
1 | # Volley简介
2 | >Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络上的图片。除了简单易用之外,Volley在性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。
3 |
4 | ###下载Volley
5 | -使用Git clone 下来
6 |
7 | ```
8 | git clone https://android.googlesource.com/platform/frameworks/volley
9 | ```
10 | - 下载地址是:http://download.csdn.net/detail/sinyu890807/7152015
11 |
12 | ###使用别人的volley镜像
13 |
14 | ```
15 | compile 'com.mcxiaoke.volley:library:1.0.19'
16 | ```
17 | ###StringReq的用法
18 | - 首先需要获取到一个RequestQueue对象,可以调用如下方法获取到:
19 |
20 | ```java
21 | RequestQueue mQueue = Volley.newRequestQueue(context);
22 | ```
23 | - 注意这里拿到的RequestQueue是一个请求队列对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。
24 | RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的,基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。
25 |
26 | - 接下来为了要发出一条HTTP请求,我们还需要创建一个StringRequest对象,如下所示:
27 |
28 | ```java
29 | StringRequest stringRequest = new StringRequest("http://www.baidu.com",
30 | new Response.Listener() {
31 | @Override
32 | public void onResponse(String response) {
33 | Log.d("TAG", response);
34 | }
35 | }, new Response.ErrorListener() {
36 | @Override
37 | public void onErrorResponse(VolleyError error) {
38 | Log.e("TAG", error.getMessage(), error);
39 | }
40 | });
41 | ```
42 | 可以看到,这里new出了一个StringRequest对象,StringRequest的构造函数需要传入三个参数。
43 | - 第一个参数就是目标服务器的URL地址,目标服务器地址我们填写的是百度的首页。
44 | - 第二个参数是服务器响应成功的回调,在响应成功的回调里打印出服务器返回的内容。
45 | - 第三个参数是服务器响应失败的回调,在响应失败的回调里打印出失败的详细信息。
46 | 最后,将这个StringRequest对象添加到RequestQueue里面就可以了,如下所示:
47 |
48 | ```java
49 | mQueue.add(stringRequest);
50 | ```
51 | - 由于Volley是要访问网络的,因此不要忘记在你的AndroidManifest.xml中添加如下权限:
52 |
53 | ```xml
54 |
55 | ```
56 | 这样的话,一个最基本的HTTP发送与响应的功能就完成了。你会发现根本还没写几行代码就轻易实现了这个功能,主要就是进行了以下三步操作:
57 | 1.创建一个RequestQueue对象。
58 | 2.创建一个StringRequest对象。
59 | 3. 将StringRequest对象添加到RequestQueue里面。
60 |
61 | HTTP的请求类型通常有两种,GET和POST,刚才我们使用的明显是一个GET请求,那么如果想要发出一条POST请求应该怎么做呢?StringRequest中还提供了另外一种四个参数的构造函数,其中第一个参数就是指定请求类型的,我们可以使用如下方式进行指定:
62 |
63 | ```java
64 | StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener);
65 | ```
66 | 当发出POST请求的时候,Volley会尝试调用StringRequest的父类——Request中的getParams()方法来获取POST参数,那么解决方法自然也就有了,我们只需要在StringRequest的匿名类中重写getParams()方法,在这里设置POST参数就可以了,代码如下所示:
67 |
68 | ```java
69 | StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {
70 | @Override
71 | protected Map getParams() throws AuthFailureError {
72 | Map map = new HashMap();
73 | map.put("params1", "value1");
74 | map.put("params2", "value2");
75 | return map;
76 | }
77 | };
78 | ```
79 | ###JsonRequest的用法
80 |
81 | 类似于StringRequest,JsonRequest也是继承自Request类的,不过由于JsonRequest是一个抽象类,因此我们无法直接创建它的实例,那么只能从它的子类入手了。
82 | JsonRequest有两个直接的子类.
83 | - JsonObjectRequest,是用于请求一段**JSON数据**的
84 | - JsonArrayRequest,是用于请求一段**JSON数组**的
85 |
86 | ```java
87 | JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,
88 | new Response.Listener() {
89 | @Override
90 | public void onResponse(JSONObject response) {
91 | Log.d("TAG", response.toString());
92 | }
93 | }, new Response.ErrorListener() {
94 | @Override
95 | public void onErrorResponse(VolleyError error) {
96 | Log.e("TAG", error.getMessage(), error);
97 | }
98 | });
99 | ```
100 | 这里我们填写的URL地址是http://m.weather.com.cn/data/101010100.html,这是中国天气网提供的一个查询天气信息的接口,响应的数据就是以JSON格式返回的,然后我们在onResponse()方法中将返回的数据打印出来。
101 | 最后再将这个JsonObjectRequest对象添加到RequestQueue里就可以了,如下所示:
102 |
103 | ```java
104 | mQueue.add(jsonObjectRequest);
105 | ```
106 | ###ImageRequest的用法
107 | 1. 创建一个RequestQueue对象。
108 | 2. 创建一个Request对象。
109 | 3. 将Request对象添加到RequestQueue里面。
110 |
111 | - 首先需要获取到一个RequestQueue对象,可以调用如下方法获取到:
112 |
113 | ```java
114 | RequestQueue mQueue = Volley.newRequestQueue(context);
115 | ```
116 | - 接下来自然要去new出一个ImageRequest对象了,代码如下所示:
117 |
118 | ```java
119 | ImageRequest imageRequest = new ImageRequest(
120 | "http://developer.android.com/images/home/aw_dac.png",
121 | new Response.Listener() {
122 | @Override
123 | public void onResponse(Bitmap response) {
124 | imageView.setImageBitmap(response);
125 | }
126 | }, 0, 0, Config.RGB_565, new Response.ErrorListener() {
127 | @Override
128 | public void onErrorResponse(VolleyError error) {
129 | imageView.setImageResource(R.drawable.default_image);
130 | }
131 | });
132 | ```
133 | - 第一个参数就是图片的URL地址
134 | - 第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中
135 | - 第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。
136 | - 第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。
137 | - 第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。
138 |
139 |
140 | 最后将这个ImageRequest对象添加到RequestQueue里就可以了,如下所示:
141 |
142 | ```java
143 | mQueue.add(imageRequest);
144 | ```
145 | ###ImageLoader的用法
146 | 1. 创建一个RequestQueue对象。
147 | 2. 创建一个ImageLoader对象。
148 | 3. 获取一个ImageListener对象。
149 | 4. 调用ImageLoader的get()方法加载网络上的图片。
150 |
151 | - 创建RequestQueue对象
152 |
153 | ```java
154 | RequestQueue mQueue = Volley.newRequestQueue(context);
155 | ```
156 | - 新建一个ImageLoader对象
157 |
158 | ```java
159 | ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {
160 | @Override
161 | public void putBitmap(String url, Bitmap bitmap) {
162 | }
163 |
164 | @Override
165 | public Bitmap getBitmap(String url) {
166 | return null;
167 | }
168 | });
169 | ```
170 | - 第一个参数就是RequestQueue对象
171 | - 第二个参数是一个ImageCache对象,这里我们先new出一个空的ImageCache的实现即可。
172 |
173 | - 接下来需要获取一个ImageListener对象
174 |
175 | ```java
176 | ImageListener listener = ImageLoader.getImageListener(imageView,
177 | R.drawable.default_image, R.drawable.failed_image);
178 | ```
179 | getImageListener()方法接收三个参数:
180 | - 第一个参数指定用于显示图片的ImageView控件
181 | - 第二个参数指定加载图片的过程中显示的图片
182 | - 第三个参数指定加载图片失败的情况下显示的图片。
183 |
184 | - 最后,调用ImageLoader的get()方法来加载图片
185 |
186 | ```java
187 | imageLoader.get("http://192.168.1.105:8080/apple.png", listener);
188 | ```
189 | - 第一个参数就是图片的URL地址
190 | - 第二个参数则是刚刚获取到的ImageListener对象
191 | - 如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度,如下所示:
192 |
193 | ```java
194 | imageLoader.get("http://192.168.1.105:8080/apple.png",
195 | listener, 200, 200);
196 | ```
197 | 现在运行一下程序并开始加载图片,你将看到ImageView中会先显示一张默认的图片,等到网络上的图片加载完成后,ImageView则会自动显示该图,
198 |
199 | 虽然现在我们已经掌握了ImageLoader的用法,但是刚才介绍的ImageLoader的优点却还没有使用到。为什么呢?因为这里创建的ImageCache对象是一个空的实现,完全没能起到图片缓存的作用。其实写一个ImageCache也非常简单,但是如果想要写一个性能非常好的ImageCache,最好就要借助Android提供的LruCache功能了,欲了解可看:http://blog.csdn.net/guolin_blog/article/details/9316683
200 |
201 | - 这里我们新建一个BitmapCache并实现了ImageCache接口,如下所示:
202 |
203 | ```java
204 | public class BitmapCache implements ImageCache {
205 |
206 | private LruCache mCache;
207 |
208 | public BitmapCache() {
209 | int maxSize = 10 * 1024 * 1024;
210 | mCache = new LruCache(maxSize) {
211 | @Override
212 | protected int sizeOf(String key, Bitmap bitmap) {
213 | return bitmap.getRowBytes() * bitmap.getHeight();
214 | }
215 | };
216 | }
217 |
218 | @Override
219 | public Bitmap getBitmap(String url) {
220 | return mCache.get(url);
221 | }
222 |
223 | @Override
224 | public void putBitmap(String url, Bitmap bitmap) {
225 | mCache.put(url, bitmap);
226 | }
227 |
228 | }
229 | ```
230 | - 可以看到,这里我们将缓存图片的大小设置为10M。接着修改创建ImageLoader实例的代码,第二个参数传入BitmapCache的实例,如下所示:
231 |
232 | ```java
233 | ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());
234 |
235 | ```
236 | ###NetworkImageView的用法
237 | >NetworkImageView是一个自定义控制,它是继承自ImageView的,具备ImageView控件的所有功能,并且在原生的基础之上加入了加载网络图片的功能。
238 |
239 | - NetworkImageView控件的用法要比前两种方式更加简单,大致可以分为以下五步:
240 |
241 | 1. 创建一个RequestQueue对象。
242 | 2. 创建一个ImageLoader对象。
243 | 3. 在布局文件中添加一个NetworkImageView控件。
244 | 4. 在代码中获取该控件的实例。
245 | 5. 设置要加载的图片地址。
246 |
247 | 其中,第一第二步和ImageLoader的用法是完全一样的,因此这里我们就从第三步开始学习了。首先修改布局文件中的代码,在里面加入NetworkImageView控件,如下所示:
248 |
249 | ```xml
250 |
254 |
255 |
260 |
261 |
267 |
268 |
269 | ```
270 | - 接着在Activity获取到这个控件的实例,这就非常简单了,代码如下所示:
271 |
272 | ```java
273 | networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
274 | ```
275 |
276 | 调用它的:
277 | - setDefaultImageResId()方法:设置加载中显示的图片
278 | - setErrorImageResId()方法:加载失败时显示的图片
279 | - setImageUrl()方法:目标图片的URL地址
280 |
281 | ```java
282 | networkImageView.setDefaultImageResId(R.drawable.default_image);
283 | networkImageView.setErrorImageResId(R.drawable.failed_image);
284 | networkImageView.setImageUrl("http://192.168.1.105:8080/apple.png",imageLoader);
285 | ```
286 | 其中,setImageUrl()方法接收两个参数
287 | - 第一个参数用于指定图片的URL地址
288 | - 第二个参数则是前面创建好的ImageLoader对象。
289 | - 将看到和使用ImageLoader来加载图片一模一样的效果
290 | - 使用ImageRequest和ImageLoader这两种方式来加载网络图片,都可以传入一个最大宽度和高度的参数来对图片进行压缩,而NetworkImageView中则完全没有提供设置最大宽度和高度的方法,那么是不是使用NetworkImageView来加载的图片都不会进行压缩呢?
291 | - NetworkImageView并不需要提供任何设置最大宽高的方法也能够对加载的图片进行压缩。
292 | - 这是由于NetworkImageView是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。
293 | - 也就是说,压缩过程是在内部完全自动化的,并不需要我们关心,NetworkImageView会始终呈现给我们一张大小刚刚好的网络图片,不会多占用任何一点内存,这也是NetworkImageView最简单好用的一点吧。
294 | - 当然了,如果你不想对图片进行压缩的话,其实也很简单,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。
295 |
296 |
297 | ---
298 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
299 | - Good Luck!
300 |
--------------------------------------------------------------------------------
/Android基础/多媒体编程笔记.md:
--------------------------------------------------------------------------------
1 | #多媒体概念
2 | * 文字、图片、音频、视频
3 |
4 | #计算机图片大小的计算
5 | > 图片大小 = 图片的总像素 * 每个像素占用的大小
6 |
7 | * 单色图:每个像素占用1/8个字节
8 | * 16色图:每个像素占用1/2个字节
9 | * 256色图:每个像素占用1个字节
10 | * 24位图:每个像素占用3个字节
11 |
12 | ---
13 | #加载大图片到内存
14 | >Android系统以ARGB表示每个像素,所以每个像素占用4个字节,很容易内存溢出
15 |
16 | ##对图片进行缩放
17 | * 获取屏幕宽高
18 |
19 | ```java
20 | Display dp = getWindowManager().getDefaultDisplay();
21 | int screenWidth = dp.getWidth();
22 | int screenHeight = dp.getHeight();
23 | ```
24 | * 获取图片宽高
25 |
26 | ```java
27 | Options opts = new Options();
28 | //请求图片属性但不申请内存
29 | opts.inJustDecodeBounds = true;
30 | BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
31 | int imageWidth = opts.outWidth;
32 | int imageHeight = opts.outHeight;
33 | ```
34 | * 图片的宽高除以屏幕宽高,算出宽和高的缩放比例,取较大值作为图片的缩放比例
35 |
36 | ```java
37 | int scale = 1;
38 | int scaleX = imageWidth / screenWidth;
39 | int scaleY = imageHeight / screenHeight;
40 | if(scaleX >= scaleY && scaleX > 1){
41 | scale = scaleX;
42 | }
43 | else if(scaleY > scaleX && scaleY > 1){
44 | scale = scaleY;
45 | }
46 | ```
47 | * 按缩放比例加载图片
48 |
49 | ```java
50 | //设置缩放比例
51 | opts.inSampleSize = scale;
52 | //为图片申请内存
53 | opts.inJustDecodeBounds = false;
54 | Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
55 | iv.setImageBitmap(bm);
56 | ```
57 |
58 | ---
59 |
60 | #在内存中创建图片的副本
61 | >直接加载的bitmap对象是只读的,无法修改,要修改图片只能在内存中创建出一个一模一样的bitmap副本,然后修改副本
62 |
63 | ```java
64 | //加载原图
65 | Bitmap srcBm = BitmapFactory.decodeFile("sdcard/photo3.jpg");
66 | iv_src.setImageBitmap(srcBm);
67 |
68 | //创建与原图大小一致的空白bitmap
69 | Bitmap copyBm = Bitmap.createBitmap(srcBm.getWidth(), srcBm.getHeight(), srcBm.getConfig());
70 | //定义画笔
71 | Paint paint = new Paint();
72 | //把纸铺在画版上
73 | Canvas canvas = new Canvas(copyBm);
74 | //把srcBm的内容绘制在copyBm上
75 | canvas.drawBitmap(srcBm, new Matrix(), paint);
76 |
77 | iv_copy.setImageBitmap(copyBm);
78 | ```
79 |
80 | ##对图片进行特效处理
81 | * 首先定义一个矩阵对象
82 |
83 | ```java
84 | Matrix mt = new Matrix();
85 | ```
86 | * 缩放效果
87 |
88 | ```java
89 | //x轴缩放1倍,y轴缩放0.5倍
90 | mt.setScale(1, 0.5f);
91 | ```
92 | * 旋转效果
93 |
94 | ```java
95 | //以copyBm.getWidth() / 2, copyBm.getHeight() / 2点为轴点,顺时旋转30度
96 | mt.setRotate(30, copyBm.getWidth() / 2, copyBm.getHeight() / 2);
97 | ```
98 | * 平移
99 |
100 | ```java
101 | //x轴坐标+10,y轴坐标+20
102 | mt.setTranslate(10, 20);
103 | ```
104 | * 镜面
105 |
106 | ```java
107 | //把X坐标都变成负数
108 | mt.setScale(-1, 1);
109 | //图片整体向右移
110 | mt.postTranslate(copyBm.getWidth(), 0);
111 | ```
112 | * 倒影
113 |
114 | ```java
115 | //把Y坐标都变成负数
116 | mt.setScale(1, -1);
117 | //图片整体向下移
118 | mt.postTranslate(0, copyBm.getHeight());
119 | ```
120 | ---
121 | #画画板
122 | >记录用户触摸事件的XY坐标,绘制直线
123 | * 给ImageView设置触摸侦听,得到用户的触摸事件,并获知用户触摸ImageView的坐标
124 |
125 | ```java
126 | iv.setOnTouchListener(new OnTouchListener() {
127 |
128 | @Override
129 | public boolean onTouch(View v, MotionEvent event) {
130 | // TODO Auto-generated method stub
131 | switch (event.getAction()) {
132 | //触摸屏幕
133 | case MotionEvent.ACTION_DOWN:
134 | //得到触摸屏幕时手指的坐标
135 | startX = (int) event.getX();
136 | startY = (int) event.getY();
137 | break;
138 | //在屏幕上滑动
139 | case MotionEvent.ACTION_MOVE:
140 | //用户滑动手指,坐标不断的改变,获取最新坐标
141 | int newX = (int) event.getX();
142 | int newY = (int) event.getY();
143 | //用上次onTouch方法得到的坐标和本次得到的坐标绘制直线
144 | canvas.drawLine(startX, startY, newX, newY, paint);
145 | iv.setImageBitmap(copyBm);
146 | startX = newX;
147 | startY = newY;
148 | break;
149 |
150 | }
151 | return true;
152 | }
153 | });
154 | ```
155 | * 刷子效果,加粗画笔
156 |
157 | ```java
158 | paint.setStrokeWidth(8);
159 | ```
160 | * 调色板,改变画笔颜色
161 |
162 | ```java
163 | paint.setColor(Color.GREEN);
164 | ```
165 | * 保存图片至SD卡
166 |
167 | ```java
168 | FileOutputStream fos = null;
169 | try {
170 | fos = new FileOutputStream(new File("sdcard/dazuo.png"));
171 | } catch (FileNotFoundException e) {
172 | // TODO Auto-generated catch block
173 | e.printStackTrace();
174 | }
175 | //保存图片
176 | copyBm.compress(CompressFormat.PNG, 100, fos);
177 | ```
178 | * 系统每次收到SD卡就绪广播时,都会去遍历sd卡的所有文件和文件夹,把遍历到的所有多媒体文件都在MediaStore数据库保存一个索引,这个索引包含多媒体文件的文件名、路径、大小
179 | * 图库每次打开时,并不会去遍历sd卡获取图片,而是通过内容提供者从MediaStore数据库中获取图片的信息,然后读取该图片
180 | * 系统开机或者点击加载sd卡按钮时,系统会发送sd卡就绪广播,我们也可以手动发送就绪广播
181 |
182 | ```java
183 | Intent intent = new Intent();
184 | intent.setAction(Intent.ACTION_MEDIA_MOUNTED);
185 | intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory()));
186 | sendBroadcast(intent);
187 | ```
188 | ---
189 | #撕衣服
190 | * 原理:把穿内衣和穿外衣的照片重叠显示,内衣照在下面,用户滑动屏幕时,触摸的是外衣照,把手指经过的像素都置为透明,内衣照就显示出来了
191 |
192 | ```java
193 | iv.setOnTouchListener(new OnTouchListener() {
194 |
195 | @Override
196 | public boolean onTouch(View v, MotionEvent event) {
197 | switch (event.getAction()) {
198 | case MotionEvent.ACTION_MOVE:
199 | int newX = (int) event.getX();
200 | int newY = (int) event.getY();
201 | //把指定的像素变成透明
202 | copyBm.setPixel(newX, newY, Color.TRANSPARENT);
203 | iv.setImageBitmap(copyBm);
204 | break;
205 |
206 | }
207 | return true;
208 | }
209 | });
210 | ```
211 | * 每次只设置一个像素点太慢,以触摸的像素为圆心,半径为5画圆,圆内的像素全部置为透明
212 |
213 | ```java
214 | for (int i = -5; i < 6; i++) {
215 | for (int j = -5; j < 6; j++) {
216 | if(Math.sqrt(i * i + j * j) <= 5)
217 | copyBm.setPixel(newX + i, newY + j, Color.TRANSPARENT);
218 | }
219 | }
220 | ```
221 | ---
222 | #音乐播放器
223 | ##播放服务
224 | * 播放音频的代码应该运行在服务中,定义一个播放服务MusicService
225 | * 服务里定义play、stop、pause、continuePlay等方法
226 |
227 | ```java
228 | private void play() {
229 | // TODO Auto-generated method stub
230 | player.reset();
231 | try {
232 | player.setDataSource("sdcard/bzj.mp3");
233 | player.prepare();
234 | } catch (Exception e) {
235 | // TODO Auto-generated catch block
236 | e.printStackTrace();
237 | }
238 | player.start();
239 |
240 | }
241 | private void pause() {
242 | player.pause();
243 | }
244 | private void stop() {
245 | player.stop();
246 | }
247 | private void continuePlay() {
248 | player.start();
249 | }
250 | ```
251 | * 把这几个方法抽取成一个接口MusicInterface
252 | * 定义一个中间人类,继承Binder,实现MusicInterface
253 | * 先start启动MusicService,再bind
254 |
255 | ```
256 | Intent intent = new Intent(this, MusicService.class);
257 | startService(intent);
258 | bindService(intent, conn, BIND_AUTO_CREATE);
259 | ```
260 | ##根据播放进度设置进度条
261 | * 获取当前的播放时间和当前音频的最长时间
262 |
263 | ```java
264 | int currentPosition = player.getCurrentPosition();
265 | int duration = player.getDuration();
266 | ```
267 | * 播放进度需要不停的获取,不停的刷新进度条,使用计时器每500毫秒获取一次播放进度
268 | * 发消息至Handler,把播放进度放进Message对象中,在Handler中更新SeekBar的进度
269 |
270 | ```java
271 | Timer timer = new Timer();
272 | timer.schedule(new TimerTask() {
273 |
274 | @Override
275 | public void run() {
276 | int currentPosition = player.getCurrentPosition();
277 | int duration = player.getDuration();
278 | Message msg = Message.obtain();
279 | //把播放进度存入Message中
280 | Bundle data = new Bundle();
281 | data.putInt("currentPosition", currentPosition);
282 | data.putInt("duration", duration);
283 | msg.setData(data);
284 | MainActivity.handler.sendMessage(msg);
285 | }
286 | }, 5, 500);
287 | ```
288 | * 在Activity中定义Handler
289 |
290 | ```java
291 | static Handler handler = new Handler(){
292 | public void handleMessage(android.os.Message msg) {
293 | //取出消息携带的数据
294 | Bundle data = msg.getData();
295 | int currentPosition = data.getInt("currentPosition");
296 | int duration = data.getInt("duration");
297 |
298 | //设置播放进度
299 | sb.setMax(duration);
300 | sb.setProgress(currentPosition);
301 | };
302 | };
303 | ```
304 | ##拖动进度条改变播放进度
305 |
306 | ```java
307 | //给sb设置一个拖动侦听
308 | sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
309 | //停止拖动时调用
310 | @Override
311 | public void onStopTrackingTouch(SeekBar seekBar) {
312 | // TODO Auto-generated method stub
313 | int progress = seekBar.getProgress();
314 | mi.seekTo(progress);
315 | }
316 | //开始拖动时调用
317 | @Override
318 | public void onStartTrackingTouch(SeekBar seekBar) {
319 | // TODO Auto-generated method stub
320 |
321 | }
322 | //拖动的时候不断调用
323 | @Override
324 | public void onProgressChanged(SeekBar seekBar, int progress,
325 | boolean fromUser) {
326 | // TODO Auto-generated method stub
327 |
328 | }
329 | });
330 | ```
331 | ---
332 | #视频播放器
333 | ##SurfaceView
334 | * 对画面的实时更新要求较高
335 | * 双缓冲技术:内存中有两个画布,A画布显示至屏幕,B画布在内存中绘制下一帧画面,绘制完毕后B显示至屏幕,A在内存中继续绘制下一帧画面
336 | * 播放视频也是用MediaPlayer,不过跟音频不同,要设置显示在哪个SurfaceView
337 |
338 | ```java
339 | SurfaceView sv = (SurfaceView) findViewById(R.id.sv);
340 | SurfaceHolder sh = sv.getHolder();
341 |
342 | MediaPlayer player = new MediaPlayer();
343 | player.reset();
344 | try {
345 | player.setDataSource("sdcard/2.3gp");
346 | player.setDisplay(sh);
347 | player.prepare();
348 | } catch (Exception e) {
349 | e.printStackTrace();
350 | }
351 | player.start();
352 | ```
353 | * SurfaceView是重量级组件,可见时才会创建
354 | * 给SurfaceHolder设置CallBack,类似于侦听,可以知道SurfaceView的状态
355 |
356 | ```java
357 | sh.addCallback(new Callback() {
358 | //SurfaceView销毁时调用
359 | @Override
360 | public void surfaceDestroyed(SurfaceHolder holder) {
361 | // TODO Auto-generated method stub
362 |
363 | }
364 | //SurfaceView创建时调用
365 | @Override
366 | public void surfaceCreated(SurfaceHolder holder) {
367 | // TODO Auto-generated method stub
368 |
369 | }
370 |
371 | @Override
372 | public void surfaceChanged(SurfaceHolder holder, int format, int width,
373 | int height) {
374 | // TODO Auto-generated method stub
375 |
376 | }
377 | });
378 | ```
379 | * SurfaceView一旦不可见,就会被销毁,一旦可见,就会被创建,销毁时停止播放,再次创建时再开始播放
380 |
381 |
382 | ---
383 | #摄像头
384 | * 启动系统提供的拍照程序
385 |
386 | ```java
387 | //隐式启动系统提供的拍照Activity
388 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
389 | //设置照片的保存路径
390 | File file = new File(Environment.getExternalStorageDirectory(), "haha.jpg");
391 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
392 | startActivityForResult(intent, 0);
393 | ```
394 | * 启动系统提供的摄像程序
395 |
396 | ```java
397 | Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
398 |
399 | File file = new File(Environment.getExternalStorageDirectory(), "haha.3gp");
400 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
401 | //设置保存视频文件的质量
402 | intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
403 | startActivityForResult(intent, 0);
404 | ```
405 | ---
406 | - 邮箱 :elisabethzhen@163.com
407 | - Good Luck!
408 |
--------------------------------------------------------------------------------
/Android基础/数据存储和界面展现day2笔记.md:
--------------------------------------------------------------------------------
1 | #测试
2 | ###按岗位划分
3 | * 黑盒测试:测试逻辑业务
4 | * 白盒测试:测试逻辑方法
5 |
6 | ###按测试粒度分
7 | * 方法测试:function test
8 | * 单元测试:unit test
9 | * 集成测试:integration test
10 | * 系统测试:system test
11 |
12 | ###按测试暴力程度
13 | * 冒烟测试:smoke test
14 | * 压力测试:pressure test
15 |
16 | ------
17 | #**单元测试junit**
18 | * 定义一个类继承AndroidTestCase,在类中定义方法,即可测试该方法
19 |
20 |
21 | * 在指定指令集时,targetPackage指定你要测试的应用的包名
22 |
23 |
27 |
28 | * 定义使用的类库
29 |
30 |
31 |
32 | * 断言的作用,检测运行结果和预期是否一致
33 | * 如果应用出现异常,会抛给测试框架
34 |
35 | ```java
36 | dependencies {
37 | testCompile 'junit:junit:4.12'
38 | testCompile "org.mockito:mockito-core:1.9.5"
39 | }
40 | ```
41 | ##"Method ... not mocked."
42 |
43 | ```
44 | android {
45 | // ...
46 | testOptions {
47 | unitTests.returnDefaultValues = true
48 | }
49 | }
50 | ```
51 | ###实例演示(AndroidStudio环境下)
52 | 首先,创建一个非常简单的被测类:Calculator类。
53 |
54 |
55 | 然后,向类中添加一些基本的算术运算方法,比如加法和减法。将下列代码复制到编辑器中。不用担心实际的实现,暂时让所有的方法返回0。
56 |
57 | ####Calculator.java
58 |
59 | ```java
60 | package com.example.testing.testingexample;
61 |
62 | public class Calculator {
63 |
64 | public double sum(double a, double b){
65 | return 0;
66 | }
67 |
68 | public double substract(double a, double b){
69 | return 0;
70 | }
71 |
72 | public double divide(double a, double b){
73 | return 0;
74 | }
75 |
76 | public double multiply(double a, double b){
77 | return 0;
78 | }
79 | }
80 | ```
81 | Android Studio提供了一个快速创建测试类的方法。只需在编辑器内右键点击Calculator类的声明,选择Go to > Test,然后"Create a new test…"
82 |
83 |
84 | 在打开的对话窗口中,选择JUnit4和"setUp/@Before",同时为所有的计算器运算生成测试方法。
85 |
86 |
87 | 这样,就会在正确的文件夹内(app/src/test/java/com/example/testing/testingexample)生成测试类框架,在框架内填入测试方法即可。下面是一个示例:
88 |
89 | ####Calculator.java
90 |
91 | ```java
92 | package com.example.testing.testingexample;
93 |
94 | import org.junit.Before;
95 | import org.junit.Test;
96 |
97 | import static org.junit.Assert.*;
98 |
99 | public class CalculatorTest {
100 |
101 | private Calculator mCalculator;
102 |
103 | @Before
104 | public void setUp() throws Exception {
105 | mCalculator = new Calculator();
106 | }
107 |
108 | @Test
109 | public void testSum() throws Exception {
110 | //expected: 6, sum of 1 and 5
111 | assertEquals(6d, mCalculator.sum(1d, 5d), 0);
112 | }
113 |
114 | @Test
115 | public void testSubstract() throws Exception {
116 | assertEquals(1d, mCalculator.substract(5d, 4d), 0);
117 | }
118 |
119 | @Test
120 | public void testDivide() throws Exception {
121 | assertEquals(4d, mCalculator.divide(20d, 5d), 0);
122 | }
123 |
124 | @Test
125 | public void testMultiply() throws Exception {
126 | assertEquals(10d, mCalculator.multiply(2d, 5d), 0);
127 | }
128 | }
129 | ```
130 | 请将代码复制到编辑器或者使用JUnit框架提供的断言来编写自己的测试。
131 |
132 | ###运行单元测试
133 | 终于到运行测试的时候了!右键点击CalculatorTest类,选择Run > CalculatorTest。也可以通过命令行运行测试,在工程目录内输入:
134 |
135 | ./gradlew test
136 | 无论如何运行测试,都应该看到输出显示4个测试都失败了。这是预期的结果,因为我们还没有实现运算操作。
137 |
138 |
139 | 让我们修改Calculator类中的sum(double a, double b)方法返回一个正确的结果,重新运行测试。你应该看到4个测试中的3个失败了。
140 |
141 | ####Calculator.java
142 |
143 | ```java
144 | public double sum(double a, double b){
145 | return a + b;
146 | }
147 | ```
148 | 作为练习,你可以实现剩余的方法使所有的测试通过。
149 |
150 | >可能你已经注意到了Android Studio从来没有让你连接设备或者启动模拟器来运行测试。那是因为,位于src/tests目录下的测试是运行在本地电脑Java虚拟机上的单元测试。编写测试,实现功能使测试通过,然后再添加更多的测试...这种工作方式使快速迭代成为可能,我们称之为**测试驱动开发**。
151 | 值得注意的是,当在本地运行测试时,Gradle为你在环境变量中提供了包含Android框架的android.jar包。但是它们功能不完整(所以,打个比方,你不能单纯调用Activity的方法并指望它们生效)。推荐使用Mockito等mocking框架来mock你需要使用的任何Android方法。对于运行在设备上,并充分利用Android框架的测试
152 | -----
153 | #SQLite数据库
154 |
155 | * 轻量级关系型数据库
156 | * 创建数据库需要使用的api:SQLiteOpenHelper
157 |
158 | ```java
159 | //arg1:数据库文件的名字
160 | //arg2:游标工厂
161 | //arg3:数据库版本
162 | public MyOpenHelper(Context context, String name, CursorFactory factory, int version){}
163 | ```
164 | 必须定义一个构造方法:
165 | 数据库被创建时会调用:onCreate方法
166 | 数据库升级时会调用:onUpgrade方法
167 |
168 | ###创建数据库
169 |
170 | ```java
171 | //创建OpenHelper对象
172 | MyOpenHelper oh = new MyOpenHelper(getContext(), "person.db", null, 1);
173 | //获得数据库对象,如果数据库不存在,先创建数据库,后获得,如果存在,则直接获得
174 | SQLiteDatabase db = oh.getWritableDatabase();
175 | ```
176 | * getWritableDatabase():打开可读写的数据库
177 | * getReadableDatabase():在磁盘空间不足时打开只读数据库,否则打开可读写数据库
178 | * 在创建数据库时创建表
179 |
180 | ```java
181 | public void onCreate(SQLiteDatabase db) {
182 | // TODO Auto-generated method stub
183 | db.execSQL("create table person (_id integer primary key autoincrement, name char(10), phone char(20), money integer(20))");
184 | }
185 | ```
186 | ---
187 | #数据库的增删改查
188 | ###SQL语句
189 | * insert into person (name, phone, money) values ('张三', '159874611', 2000);
190 | * delete from person where name = '李四' and _id = 4;
191 | * update person set money = 6000 where name = '李四';
192 | * select name, phone from person where name = '张三';
193 |
194 | ###执行SQL语句实现增删改查
195 | ```java
196 | //插入
197 | db.execSQL("insert into person (name, phone, money) values (?, ?, ?);", new Object[]{"张三", 15987461, 75000});
198 | //查找
199 | Cursor cs = db.rawQuery("select _id, name, money from person where name = ?;", new String[]{"张三"});
200 | ```
201 | * 测试方法执行前会调用此方法
202 |
203 | ```java
204 | protected void setUp() throws Exception {
205 | super.setUp();
206 | // 获取虚拟上下文对象
207 | oh = new MyOpenHelper(getContext(), "people.db", null, 1);
208 | }
209 | ```
210 | ###使用api实现增删改查
211 | * 插入
212 |
213 | ```java
214 | //以键值对的形式保存要存入数据库的数据
215 | ContentValues cv = new ContentValues();
216 | cv.put("name", "刘能");
217 | cv.put("phone", 1651646);
218 | cv.put("money", 3500);
219 | //返回值是改行的主键,如果出错返回-1
220 | long i = db.insert("person", null, cv);
221 | ```
222 | * 删除
223 |
224 | ```java
225 | //返回值是删除的行数
226 | int i = db.delete("person", "_id = ? and name = ?", new String[]{"1", "张三"});
227 | ```
228 | * 修改
229 |
230 | ```java
231 | ContentValues cv = new ContentValues();
232 | cv.put("money", 25000);
233 | int i = db.update("person", cv, "name = ?", new String[]{"赵四"});
234 | ```
235 | * 查询
236 |
237 | ```java
238 | //arg1:要查询的字段
239 | //arg2:查询条件
240 | //arg3:填充查询条件的占位符
241 | Cursor cs = db.query("person", new String[]{"name", "money"}, "name = ?", new String[]{"张三"}, null, null, null);
242 | while(cs.moveToNext()){
243 | // 获取指定列的索引值
244 | String name = cs.getString(cs.getColumnIndex("name"));
245 | String money = cs.getString(cs.getColumnIndex("money"));
246 | System.out.println(name + ";" + money);
247 | }
248 | ```
249 | ###事务
250 | * 保证多条SQL语句要么同时成功,要么同时失败
251 | * 最常见案例:银行转账
252 | * 事务api
253 |
254 | ```java
255 | try {
256 | //开启事务
257 | db.beginTransaction();
258 | ...........
259 | //设置事务执行成功
260 | db.setTransactionSuccessful();
261 | } finally{
262 | //关闭事务
263 | //如果此时已经设置事务执行成功,则sql语句生效,否则不生效
264 | db.endTransaction();
265 | }
266 | ```
267 | ---
268 | #把数据库的数据显示至屏幕
269 | 1. 任意插入一些数据
270 | * 定义业务bean:Person.java
271 | * 读取数据库的所有数据
272 |
273 | ```java
274 | Cursor cs = db.query("person", null, null, null, null, null, null);
275 | while(cs.moveToNext()){
276 | String name = cs.getString(cs.getColumnIndex("name"));
277 | String phone = cs.getString(cs.getColumnIndex("phone"));
278 | String money = cs.getString(cs.getColumnIndex("money"));
279 | //把读到的数据封装至Person对象
280 | Person p = new Person(name, phone, money);
281 | //把person对象保存至集合中
282 | people.add(p);
283 | }
284 | ```
285 | * 把集合中的数据显示至屏幕
286 |
287 | ```java
288 | LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
289 | for(Person p : people){
290 | //创建TextView,每条数据用一个文本框显示
291 | TextView tv = new TextView(this);
292 | tv.setText(p.toString());
293 | //把文本框设置为ll的子节点
294 | ll.addView(tv);
295 | }
296 | ```
297 | ###添加ScrollView使得滑动
298 | ```xml
299 |
300 | ......
301 |
302 | ```
303 | * 分页查询
304 |
305 | ```java
306 | Cursor cs = db.query("person", null, null, null, null, null, null, "0, 10");
307 | ```
308 | ---
309 | #ListView
310 | * 就是用来显示一行一行的条目的
311 | * MVC结构
312 | * M:model模型层,要显示的数据 javaBean ————people集合
313 | * V:view视图层,用户看到的界面 jsp ————ListView
314 | * c:control控制层,操作数据如何显示 servlet ————adapter对象
315 | * 每一个条目(Item)都是一个View对象
316 |
317 | ###BaseAdapter
318 | * 必须实现的两个方法
319 |
320 | 第一个
321 |
322 | ```java
323 | //系统调用此方法,用来获知模型层有多少条数据
324 | @Override
325 | public int getCount() {
326 | return people.size();
327 | }
328 | ```
329 |
330 | 第二个:**position:是return的View对象所对应的数据在集合中的位置**
331 |
332 | ```java
333 | //系统调用此方法,获取要显示至ListView的View对象
334 | //position:是return的View对象所对应的数据在集合中的位置
335 | //position:本次getView方法调用所返回的view对象,在listView中是处于第几个条目,那么position的值就是多少
336 | @Override
337 | public View getView(int position, View convertView, ViewGroup parent) {
338 | System.out.println("getView方法调用" + position);
339 | TextView tv = new TextView(MainActivity.this);
340 | //拿到集合中的元素
341 | Person p = people.get(position);
342 | tv.setText(p.toString());
343 |
344 | //把TextView的对象返回出去,它会变成ListView的条目
345 | return tv;
346 | }
347 | ```
348 | * 屏幕上能显示多少个条目,getView方法就会被调用多少次,屏幕向下滑动时,getView会继续被调用,创建更多的View对象显示至屏幕
349 |
350 | ###条目的缓存
351 | * 当条目划出屏幕时,系统会把该条目缓存至内存,当该条目再次进入屏幕,系统在重新调用getView时会把缓存的条目作为convertView参数传入,但是传入的条目不一定是之前被缓存的该条目,即系统有可能在调用getView方法获取第一个条目时,传入任意一个条目的缓存
352 |
353 | ###提升ListView的运行效率
354 |
355 | ```java
356 | public class NewsAdapter extends ArrayAdapter {
357 |
358 | private int resourceId;
359 |
360 | public NewsAdapter(Context context, int textViewResourceId, List objects) {
361 | super(context, textViewResourceId, objects);
362 | resourceId = textViewResourceId;
363 |
364 | }
365 |
366 | @Override
367 | public View getView(int position, View convertView, ViewGroup parent) {
368 | News news = getItem(position);
369 | View view;
370 | ViewHolder viewHolder;
371 | if (convertView == null) {
372 | view = LayoutInflater.from(getContext()).inflate(resourceId, null);
373 | viewHolder = new ViewHolder();
374 | viewHolder.newsTitleText = (TextView) view.findViewById(R.id.news_title);
375 | view.setTag(viewHolder);//将ViewHolder存储在View中
376 | } else {
377 | view = convertView;
378 | viewHolder = (ViewHolder) view.getTag();//重新获取ViewHolder
379 | }
380 | //TextView newsTitleText = (TextView) view.findViewById(R.id.news_title);
381 |
382 | viewHolder.newsTitleText.setText(news.getTitle());
383 | return view;
384 | }
385 |
386 | class ViewHolder {
387 | TextView newsTitleText;
388 | }
389 | }
390 | ```
391 | ---
392 | #对话框
393 | ###确定取消对话框
394 | * 创建对话框构建器对象,类似工厂模式
395 |
396 | ```java
397 | AlertDialog.Builder builder = new Builder(this);
398 | ```
399 | * 设置标题和正文
400 |
401 | ```java
402 | builder.setTitle("警告");
403 | builder.setMessage("若练此功,必先自宫");
404 | ```
405 | * 设置确定和取消按钮
406 |
407 | ```java
408 | builder.setPositiveButton("现在自宫", new OnClickListener() {
409 |
410 | @Override
411 | public void onClick(DialogInterface dialog, int which) {
412 | // TODO Auto-generated method stub
413 | Toast.makeText(MainActivity.this, "恭喜你自宫成功,现在程序退出", 0).show();
414 | }
415 | });
416 |
417 | builder.setNegativeButton("下次再说", new OnClickListener() {
418 |
419 | @Override
420 | public void onClick(DialogInterface dialog, int which) {
421 | // TODO Auto-generated method stub
422 | Toast.makeText(MainActivity.this, "若不自宫,一定不成功", 0).show();
423 | }
424 | });
425 | ```
426 | * 使用构建器创建出对话框对象
427 |
428 | ```java
429 | AlertDialog ad = builder.create();
430 | ad.show();
431 | ```
432 | ###单选对话框
433 |
434 | ```java
435 | AlertDialog.Builder builder = new Builder(this);
436 | builder.setTitle("选择你的性别");
437 | ```
438 | * 定义单选选项
439 |
440 | ```java
441 | final String[] items = new String[]{
442 | "男", "女", "其他"
443 | };
444 | //-1表示没有默认选择
445 | //点击侦听的导包要注意别导错
446 | builder.setSingleChoiceItems(items, -1, new OnClickListener() {
447 |
448 | //which表示点击的是哪一个选项
449 | @Override
450 | public void onClick(DialogInterface dialog, int which) {
451 | Toast.makeText(MainActivity.this, "您选择了" + items[which], 0).show();
452 | //对话框消失
453 | dialog.dismiss();
454 | }
455 | });
456 |
457 | builder.show();
458 | ```
459 | ###多选对话框
460 |
461 | ```java
462 | AlertDialog.Builder builder = new Builder(this);
463 | builder.setTitle("请选择你认为最帅的人");
464 | ```
465 | * 定义多选的选项,因为可以多选,所以需要一个boolean数组来记录哪些选项被选了
466 |
467 | ```java
468 | final String[] items = new String[]{
469 | "赵帅哥",
470 | "赵师哥",
471 | "赵老师",
472 | "侃哥"
473 | };
474 | //true表示对应位置的选项被选了
475 | final boolean[] checkedItems = new boolean[]{
476 | true,
477 | false,
478 | false,
479 | false,
480 | };
481 | builder.setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() {
482 |
483 | //点击某个选项,如果该选项之前没被选择,那么此时isChecked的值为true
484 | @Override
485 | public void onClick(DialogInterface dialog, int which, boolean isChecked) {
486 | checkedItems[which] = isChecked;
487 | }
488 | });
489 |
490 | builder.setPositiveButton("确定", new OnClickListener() {
491 |
492 | @Override
493 | public void onClick(DialogInterface dialog, int which) {
494 | StringBuffer sb = new StringBuffer();
495 | for(int i = 0;i < items.length; i++){
496 | sb.append(checkedItems[i] ? items[i] + " " : "");
497 | }
498 | Toast.makeText(MainActivity.this, sb.toString(), 0).show();
499 | }
500 | });
501 | builder.show();
502 | ```
503 | ---
504 | - 邮箱 :elisabethzhen@163.com
505 | - Good Luck!
--------------------------------------------------------------------------------
/Android进阶/【译文】RxAndroid and Kotlin(Part 1).md:
--------------------------------------------------------------------------------
1 | #RxAndroid and Kotlin (Part1)
2 | > 原文作者:Ahmed Rizwan
3 |
4 | > 原文链接:[RxAndroid and Kotlin (Part1)](https://medium.com/@ahmedrizwan/rxandroid-and-kotlin-part-1-f0382dc26ed8#.8wixgg4a5)
5 |
6 | > 译文作者:[Dimon](https://dimon94.github.io/)
7 |
8 | > 译者语:由于本人水平有限,翻译可能会存在偏差,望大大们谅解。翻译文章只为交流学习,谢谢观看。
9 |
10 | > 文中的`Subscriber`我直接译为观察者了,只是为了方便自己理解,如有不悦,见谅。
11 |
12 | ---
13 |
14 | 我开始关注[RxAndroid](https://github.com/ReactiveX/RxAndroid)已经差不多一周时间了。一开始,我并没有真正意义上地理解它...我的意思是:我抓住了它的概念,但是我却不清楚该何时使用它。不过后来看了一些例子以及一些干货文章(链接在文章末)后,我才了解了`RxAndroid`!(有了个良好的程度)当时的内心如下:
15 |
16 | 
17 |
18 | 简而言之,你几乎可以在任何地方使用`Rx`,但你不能这样做。你应该明智地揣摩哪儿更适合使用它,因为在某种情况下,`Rx`可以提升100倍以上的生产性或者比正常命令性编程更好,而在其他情况下,则是没必要使用它的。
19 |
20 | 那么以下,我将展示`Kotlin`和`Java`这两种语言关于`Rx`的一些例子。
21 |
22 | > 如果你现在对`Kotlin`不那么熟悉,那么我建议您可以访问以下链接:
23 |
24 | > [Official Kotlin Website](http://kotlinlang.org/)
25 |
26 | > [Getting Started on Android](http://kotlinlang.org/docs/tutorials/kotlin-android.html)
27 |
28 | > [Jake Wharton’s Paper on Kotlin](https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/edit)
29 |
30 | > 摘要: Kotlin is an awesometacular alternative to Java, that works amazingly well on Android. And oh, it's developed by JetBrains!
31 |
32 | > P.S. `Kotlin` 是没有分号的。:-)
33 |
34 | ---
35 |
36 | ### Rx:概念
37 |
38 | 如果你已经对于`Rx`有了很好的概念,那么你可以跳过这一part。否则...请你继续吞了下面的这颗安利!!!
39 |
40 | 那么什么是Rx呢?(⊙v⊙)蒽...其实就是反应式编程`reactive programming`...用简单的话说,反应式编程`reactive programming`其实就是一种与观察者模式`Observer Pattern`密切相关的编程模式。
41 | 其中,由被观察者`Observable`调用了观察者`Subscriber`的回调方法,就实现了由被观察者`Observable`向观察者`Subscriber`的事件传递,即观察者模式`Observer Pattern`。
42 | 
43 |
44 | `Rx`也是函数式编程的一个子集。所以经常被称为`Functional Reactive Programming`。因为...作为用户接收的数据,它们可以申请它们的转换序列。(类似于我们在Java8中的对流所做的)
45 |
46 | 
47 |
48 | 我们甚至可以结合或者说合并不同的流`streams`。它就是这么的灵活!所以,现在我们只记得有一堆不同的、我们(观察者`Subscriber`)可以从被观察者`Observable`中接收的数据。
49 |
50 | 现在,概念开始有点清晰,让我们回过头来看看RxJava。
51 |
52 | 在Rx中,观察者`subscriber`的三个事件回调方法:
53 |
54 | 1.`onNext(Data)` :从被观察者`Observale`中接收数据(相当于 onClick() / onEvent())
55 |
56 | 2.`onError(Exception)` :如果抛出异常,这个方法将被调用
57 |
58 | 3.`onCompleted()` :将在事件队列完结时调用。
59 | > `RxJava`不仅把每个事件单独处理,还会把它们看做一个队列。`RxJava` 规定,当不会再有新的 `onNext()` 发出时,需要触发 `onCompleted()` 方法作为标志。
60 |
61 | 其实这就像`Iterables`在`Java`里那样。但有所不同的是,`Iterables`是`pull`型,而Rx中的被观察者们`Observales`是`push`型,这是被观察者`Observable`将数据推到了其观察者`subscribers`中。以下是他们之间的比较表格:
62 | 
63 |
64 | > 另外需要注意的是,Rx本质上是异步的,也就是说观察者`subscribers`不会等待其他观察者`subscribers`。他们其实“异步”的过程流。
65 |
66 | 所以被观察者`Observables`推出的数据流给他们的观察者`subscribers`,然后观察者`subscribers`可以处置这些流(在上面的方法的帮助下)。我们可以通过下面的[ Marble Diagrams](http://rxmarbles.com/)来更好地了解流:
67 | 
68 |
69 | 这些圆圈代表着的是流上的数据对象。箭头代表该数据流动的一个方向,以有序的方式!再看看以下图:
70 | 
71 |
72 | 就像我之前提到的,我们可以将类似于map、filter、zip等等使用`operators`转变成数据(以及流)。上图表示的是一种简单的映射。所以这一转变后,观察者`subscriber`这个流将得到转变后的流,怎么样?酷吧!
73 |
74 | 我想你现在应该对Rx是如何工作已经有了一个很好的概念,所以现在让我们开始实际操作吧。
75 |
76 | - 好,我们要做的第一件事就是打坐。(莫打趣,其实只是想思考代码)
77 |
78 | 
79 |
80 | - 然后,创建一个被观察者`Observable`亦不是难事。
81 |
82 | 这有一些可以[创建被观察者`Observables`](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables)的方式,我只是将其中三个罗列出来:
83 |
84 | 1.`Observable.from()` : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。
85 |
86 | ```java
87 | //Kotlin
88 | Observable.from(listOf(1, 2, 3, 4, 5))
89 | //Java
90 | Observable.from(Arrays.asList(1, 2, 3, 4, 5));
91 | //它将会发出以下这些数字 : 1 - 2 - 3 - 4 - 5
92 | ```
93 |
94 | ```java
95 | String[] words = {"Hello", "Hi", "Aloha"};
96 | Observable observable = Observable.from(words);
97 | // 将会依次调用:
98 | // onNext("Hello");
99 | // onNext("Hi");
100 | // onNext("Aloha");
101 | // onCompleted();
102 | ```
103 |
104 | 2.Observable.just() :将传入的参数依次发送出来。
105 |
106 | ```java
107 | Observable observable = Observable.just("Hello", "Hi", "Aloha");
108 | // 将会依次调用:
109 | // onNext("Hello");
110 | // onNext("Hi");
111 | // onNext("Aloha");
112 | // onCompleted();
113 | ```
114 |
115 | 3.Observable.create() :创建一个 `Observable`,实现`OnSubscribe`接口,并告诉被观察者`observable`当它被订阅时,`OnSubscribe` 的 `call()` 方法会自动被调用,将数据发送到观察者`subscriber(s)`。
116 |
117 | ```java
118 | //Kotlin
119 | Observable.create(object : Observable.OnSubscribe {
120 | override fun call(subscriber: Subscriber) {
121 | for(i in 1 .. 5)
122 | subscriber.onNext(i)
123 |
124 | subscriber.onCompleted()
125 | }
126 | })
127 | ```
128 | 相同的代码的Java版本:
129 |
130 | ```java
131 | //Java
132 | Observable.create(new Observable.OnSubscribe() {
133 | @Override
134 | public void call(final Subscriber super Integer>
135 | subscriber) {
136 | for (int i = 1; i <= 5; i++)
137 | subscriber.onNext(i);
138 |
139 | subscriber.onCompleted();
140 | }
141 | });
142 | ```
143 | 上面写的代码就相当于我写了`Observable.from()`的例子,但你可以看到,我们可以充分控制发出什么东西、什么时候流可以结束。还可以使用`subscribe.onError(e)`发送捕获到的异常。
144 |
145 | 好了,到了现在,我们需要实现观察者`subscriber`。
146 |
147 | ---
148 |
149 | ###实现观察者`subscriber`
150 |
151 | 在我们已经实现被`Observables`之后,我们需要的一个`subscriber`!对于安卓而言,订阅一个被`Observable`我们首先要告诉`observable`有关我们打算订阅和观察的线程。`RxAndroid`给我们的[`Schedulers`](https://github.com/ReactiveX/RxJava/wiki/The-RxJava-Android-Module),通过它我们可以指定相应的线程。所以,让我们举一个简单的“Hello World”`observable`的例子,实现在`worker thread`完成订阅,在`main thread`上观察。
152 |
153 | ```java
154 | //Kotlin
155 | Observable.just("Hello World")
156 | .subscribeOn(Schedulers.newThread())
157 | //each subscription is going to be on a new thread.
158 | .observeOn(AndroidSchedulers.mainThread()))
159 | //observation on the main thread
160 | //Now our subscriber!
161 | .subscribe(object:Subscriber(){
162 | override fun onCompleted() {
163 | //Completed
164 | }
165 |
166 | override fun onError(e: Throwable?) {
167 | //TODO : Handle error here
168 | }
169 |
170 | override fun onNext(t: String?) {
171 | Log.e("Output",t);
172 | }
173 | })
174 | //Java
175 | Observable.just("Hello World")
176 | .subscribeOn(Schedulers.newThread())
177 | .observeOn(AndroidSchedulers.mainThread())
178 | .subscribe(new Subscriber() {
179 | @Override
180 | public void onCompleted() {
181 | //Completion
182 | }
183 |
184 | @Override
185 | public void onError(final Throwable e) {
186 | //TODO : Handle error here
187 | }
188 |
189 | @Override
190 | public void onNext(final String s) {
191 | Log.e("Output",s);
192 | }
193 | });
194 | ```
195 | > 你可以在[这里](https://github.com/ReactiveX/RxJava/wiki/The-RxJava-Android-Module)得到有关调度与线程的更多信息。
196 |
197 | 那,它是怎么工作的呢?
198 |
199 | 当你运行上面那段代码时,它会显示一个日志消息
200 |
201 | ```java
202 | Output: Hello World!
203 | ```
204 | 你看,“订阅”就是如此的简单。你可以点击[这里](http://reactivex.io/documentation/operators/subscribe.html)获取更多关于订阅的相关详情。
205 |
206 | ---
207 | ###一个简单的实例:Debounce!!!
208 | 好了,你现在知道了如何创建简单的`observables`了对不对?~
209 |
210 | 那么让我们来做我们自己喜欢的`RxExamples`~~
211 |
212 | 我呢~想实现这个:
213 |
214 | 
215 |
216 | 在这个例子里,我将文本输入到一个`EditText`中,并且针对该文本:当我输入时,一个响应将被自动触发,从而打印出我输入的文本。现在这样的反应可能被称为一个`API`的调用。所以,如果我触发此要求输入了的每一个字符——这将是一种浪费,因为我只需要知道最后一个输入,这意味着在我停止打字的时候它应该只触发一个`call`——也就是在我打字后,静止一秒的说~!
217 |
218 | 那么该如何才能在` non-reactive programming`做到这一点呢?
219 |
220 | ####Non-Reactive Solution
221 |
222 | 我用一个定时器,并且安排它在`afterTextChanged()`方法延时1000毫秒后调用`run()`方法。呃,不要忘记了也要在那添加`runOnUiThread()`。-_-
223 |
224 | 其实在概念上,实现起来并不难,但是却会让代码变得非常混乱,在`Java`中更是如此。
225 |
226 | - `Java version`
227 |
228 | ```java
229 | //Java
230 | Timer timer = new Timer();
231 |
232 | final TextView textView = (TextView) findViewById(R.id.textView);
233 | final EditText editText = (EditText) findViewById(R.id.editText);
234 |
235 | editText.addTextChangedListener(new TextWatcher() {
236 | @Override
237 | public void beforeTextChanged(CharSequence s, int start, int count,
238 | int after) {
239 | }
240 |
241 | @Override
242 | public void onTextChanged(final CharSequence s, int start, int before,
243 | int count) {
244 | if (timer != null)
245 | timer.cancel();
246 | }
247 |
248 | @Override
249 | public void afterTextChanged(final Editable s) {
250 | timer = new Timer();
251 | timer.schedule(new TimerTask() {
252 | @Override
253 | public void run() {
254 | runOnUiThread(new Runnable() {
255 | @Override
256 | public void run() {
257 | textView.setText("Output : " + editText.getText());
258 | }
259 | });
260 | }
261 |
262 | }, 1000);
263 | }
264 | });
265 | ```
266 |
267 | - Kotlin
268 |
269 | ```
270 | //Kotlin
271 | var timer: Timer? = Timer()
272 |
273 | val editTextStop = findViewById(R.id.editText) as EditText
274 | editTextStop.addTextChangedListener(object : TextWatcher {
275 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
276 |
277 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
278 | timer?.cancel()
279 | }
280 |
281 | override fun afterTextChanged(s: Editable) {
282 | timer = Timer()
283 | timer!!.schedule(object : TimerTask() {
284 | override fun run() {
285 | runOnUiThread { textView.setText("Output : " + editTextStop.getText()) }
286 | }
287 | }, 1000)
288 | }
289 | })
290 |
291 | ```
292 |
293 | ####`Reactive Solution`
294 | 反应式的解决方案是一个非常自由的样板。而且只有3个步骤。
295 |
296 | 1.创建一个`observable`
297 | 2.添加`Debounce operator`,1000毫秒(1秒)的延迟
298 | 3.订阅它
299 |
300 | - 首先是`Java`代码
301 |
302 | ```java
303 | Observable.create(new Observable.OnSubscribe() {
304 | @Override
305 | public void call(final Subscriber super String> subscriber) {
306 | editText.addTextChangedListener(new TextWatcher() {
307 | @Override
308 | public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
309 | }
310 |
311 | @Override
312 | public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
313 | subscriber.onNext(s.toString());
314 | }
315 |
316 | @Override
317 | public void afterTextChanged(final Editable s) {
318 | }
319 | });
320 | }
321 | })
322 | .debounce(1000, TimeUnit.MILLISECONDS)
323 | .observeOn(AndroidSchedulers.mainThread())
324 | .subscribe(new Action1() {
325 | @Override
326 | public void call(final String s) {
327 | textView.setText("Output : " + s);
328 | }
329 | });
330 | ```
331 |
332 | - `Kotlin`❤
333 |
334 | ```
335 | Observable.create(Observable.OnSubscribe { subscriber ->
336 | editText.addTextChangedListener(object : TextWatcher {
337 | override fun afterTextChanged(s: Editable?) = Unit
338 |
339 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
340 |
341 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int)
342 | = subscriber.onNext(s.toString())
343 | })
344 | }).debounce(1000, TimeUnit.MILLISECONDS)
345 | .observeOn(AndroidSchedulers.mainThread())
346 | .subscribe({
347 | text ->
348 | textView.text = "Output : " + text
349 | })
350 | ```
351 | ####更少的样板——`RxBindings`!
352 | 我们可以使用[RxBindings](https://github.com/JakeWharton/RxBinding)——这是`RxJava`结合了`Android`的UI组件的`API`。它适用于`Java`和`Kotlin`!快去使用它吧!!
353 |
354 | ```
355 | //Java with Retrolambda and RxBinding
356 | RxTextView.afterTextChangeEvents(editText)
357 | .debounce(1000,TimeUnit.MILLISECONDS)
358 | .observeOn(AndroidSchedulers.mainThread())
359 | .subscribe(tvChangeEvent -> {
360 | textView.setText("Output : " + tvChangeEvent.view()
361 | .getText());
362 | });
363 |
364 | //Kotlin with RxBinding
365 | RxTextView.afterTextChangeEvents(editText)
366 | .debounce(1000, TimeUnit.MILLISECONDS)
367 | .observeOn(AndroidSchedulers.mainThread())
368 | .subscribe { tvChangeEvent ->
369 | textView.text = "Output : " + tvChangeEvent.view().text
370 | }
371 | ```
372 | 正如你所看到的,更少的代码。如果在几个月前看着段代码,我很难在一分钟你弄清楚这是怎么回事。这可真是无价之宝!
373 |
374 | ---
375 |
376 | 下面是一些我推荐的关于Rx的一些资源!我将会继续研究`Rx+(kotlin&Java)`和继续完成`part2`的,敬请关注!
377 |
378 | - [Official Rx Page](http://reactivex.io/)
379 | - [Grokking RxJava Series by Dan Lew](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/)
380 | - [Android Rx, and Kotlin : A case study](http://beust.com/weblog/2015/03/23/android-rx-and-kotlin-a-case-study/)
381 | - [Replace AsyncTasks with Rx](http://stablekernel.com/blog/replace-asynctask-asynctaskloader-rx-observable-rxjava-android-patterns/)
382 | - [PhilosophicalHacker Blog on Rx](http://blog.stablekernel.com/replace-asynctask-asynctaskloader-rx-observable-rxjava-android-patterns/)
383 | - [Implementing EventBus in Rx](http://nerds.weddingpartyapp.com/tech/2014/12/24/implementing-an-event-bus-with-rxjava-rxbus/)
384 | - [RxKotlin](https://github.com/ReactiveX/RxKotlin)
385 |
386 | ---
387 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
388 | - Good Luck!
389 |
--------------------------------------------------------------------------------
/JavaWeb/JavaWeb基础笔记.md:
--------------------------------------------------------------------------------
1 | # 1.eclipse使用和程序的断点调试
2 | ### Eclipse的使用
3 | - 在eclipse下Java程序的编写和运行,及java运行环境的配置。 新建java工程day01,在弹出窗口中可配置jre 工程右键属性可配置编辑器的版本
4 |
5 | ### 调试程序 Debug窗口
6 | - Resume(F8)到下一个断点
7 | - Step into(F5)进入到函数等里面 Step over(F6)到下一行代码
8 | - Step return(F7)返回到调用的下一行
9 | - Drop to Frame返回到当前方法的第一行, Terminate (F12)终止虚拟机,程序就结束了。(调试完后用) 右键watch观察变量的值
10 |
11 | ### Breakpoints窗口
12 | - 移除所以断点
13 |
14 | ### 断点注意问题
15 | - 调完后,移除所以断点
16 | - 调完后,一定要结束断点的JVM
17 |
18 | ---
19 |
20 | # 2.eclipse常用快捷键
21 | - MyEclipse设置工作空间默认编码utf-8等,使新建工程使用默认编码 菜单栏——Window / Preferences / General / Workspace 。 内容提示:Alt + / Content Assist
22 | - 选中多行代码,按Tab键是整块向右推进,按Shift+Tab是整块向左缩进 快速修复:Ctrl + 1 导包:Ctrl + shift + O
23 | - 格式化代码块:ctrl + shift + F
24 | - 向前向后:Alt + 方向键(left right arrow)查看源代码时 添加注释 Ctrl+Shift+/ 除去注释 Ctrl+Shift+\
25 | - 查看源代码:Ctrl+单击 ctrl+shift+t 查看方法说明:F2
26 | - 重置透视图:Window menu下
27 | - 更改为大写:Ctrl+Shift+X
28 | - 更改为小写:Ctrl+Shift+Y
29 | - 复制行:Ctrl+Alt+向下键(有些不能用)
30 | - 查看类的继承关系:Ctrl+T
31 | - 查看快捷键:Ctrl+shift+L
32 |
33 | ---
34 |
35 | # 3.junit测试框架
36 | 在Outline窗口方法上右键`Run As /JUnit Test `测试某一个方法,类上右键`run as /JUnit Test` 测试这个类的所有方法
37 | 1.用`junit`进行单元测试的时候,在每个被测试的方法中必须加上`@Test`注解
38 | 2.用`@Before`注解是在每个被测试的方法前执行。
39 | 3.用`@After`注解是在每个被测试的方法后执行。
40 |
41 | ---
42 |
43 | # 4.java5的静态导入和自动装箱拆箱
44 | > JDK5中新增了很多新的java特性,利用这些新语法可以帮助开发人员编写出更加高效、清晰,安全的代码。 静态导入 自动装箱/拆箱 增强for循环 可变参数 枚举反射内省 泛型 元数据
45 |
46 | ### 静态导入
47 | `JDK 1.5` 增加的静态导入语法用于导入类的某个静态属性或方法。使用静态导入可以简化程序对类静态属性和方法的调用。 语法:
48 | `Import static` 包名.类名.静态属性|静态方法|*
49 |
50 | ### 自动装箱/拆箱
51 | `JDK5.0`的语法允许开发人员把一个基本数据类型直接赋给对应的包装类变量, 或者赋给 `Object` 类型的变量,这个
52 | 过程称之为自动装箱。
53 | 自动拆箱与自动装箱与之相反,即把包装类对象直接赋给一个对应的基本类型变量。
54 |
55 | ---
56 |
57 | # 5.增强for循环
58 | ### 增强for循环
59 | 引入增强for循环的原因:在JDK5以前的版本中,遍历数组或集合中的元素,需先获得数组的长度或集合的迭代器,比较麻烦!
60 | 因此`JDK5`中定义了一种新的语法——增强for循环,以简化此类操作。增强for循环只能用在数组、或实现`Iterator`接口的集合类上 语法格式:
61 |
62 |
63 | for(变量类型变量 :需迭代的数组或集合){}
64 |
65 | ---
66 |
67 | # 6.可变参数(相当于动态的数组)
68 | 测试JDK中具有可变参数的类Arrays.asList()方法。分别传多个参数、传数组,传数组又传参的情况。
69 | **注意:传入基本数据类型数组的问题。**
70 | 从JDK 5开始, Java 允许为方法定义长度可变的参数。语法:
71 |
72 | ```java
73 | public void foo(int … args){ }
74 | ```
75 | 注意事项:
76 | 调用可变参数的方法时, 编译器将自动创建一个数组保存传递给方法的可变参数,因此,程序员可以在方法体中以数组的形式访问可变参数
77 | 可变参数只能处于参数列表的最后,所以一个方法最多只能有一个长度可变的参数
78 |
79 | ---
80 |
81 | # 7.枚举类
82 | 为什么需要枚举?
83 | 一些方法在运行时,它需要的数据不能是任意的,而必须是一定范围内的值,此类问题在JDK5以前采用自定义带有枚举功能的类解决,Java5以后可以直接使用枚举予以解决。 JDK 5新增的 enum 关键字用于定义一个枚举类。
84 |
85 | ### 枚举类
86 | 枚举类具有如下特性:
87 |
88 | 1.枚举类也是一种特殊形式的Java类。
89 |
90 | 2.枚举类中声明的每一个枚举值代表枚举类的一个实例对象。A,B,C,D
91 |
92 | 3.与java中的普通类一样,在声明枚举类时,也可以声明属性、方法和构造函数,但枚举类的构造函数必须为私有的(private这点不难理解)。为什么?防止其他类初始化对象
93 |
94 | 4.枚举类也可以实现接口、或继承抽象类。
95 | 5.JDK5中扩展了swith语句,它除了可以接收int, byte, char, short外,还可以接收一个枚举类型。
96 |
97 | 6.若枚举类只有一个枚举值,则可以当作单态设计模式使用。
98 |
99 | 7.Java中声明的枚举类,均是java.lang.Enum类的孩子,它继承了Enum类的所有方法。
100 |
101 | 常用方法:
102 | name()
103 | ordinal()//返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
104 | valueof(Class enumClass, String name) //返回带指定名称的指定枚举类型的枚举常量。
105 | //String str=”B”;Grade g=Grade.valueOf(Grade.class,str);
106 | values() 此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便。
107 | 练习:请编写一个关于星期几的枚举WeekDay,要求: 枚举值:Mon、Tue 、Wed 、Thu 、Fri 、Sat 、Sun 该枚举要有一个方法,调用该方法返回中文格式的星期。
108 |
109 | ---
110 |
111 | # 8.反射技术
112 | ### 反射什么-Class类
113 |
114 | 1. 反射就是把Java类中的各种成分映射成一个个的java对象。例如,一个类有:成员变量,方法,构造方法,包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。
115 | 2. Class类用于表示.class文件,画图演示一个对象的创建过程。
116 | 3. 如何得到某个class文件对应的class对象。
117 | 类名.class, 对象.getClass() Class.forName(“类名”)
118 |
119 | 以上三种方式,JVM都会把相应class文件装载到一个class对象中(只装载一次) 创建类的实例:Class.newInstance方法 数组类型的Class实例对象 Class.isArray()
120 |
121 | 总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int,void„
122 |
123 | 反射就是把Java类中的各种成分映射成一个个的java对象。例如,一个类有:成员变量,方法,构造方法,包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。
124 |
125 | 掌握反射技术的要点在于:如何从一个class中反射出各个组成部分。反射出来的对象怎么用。
126 |
127 | ### Constructor类
128 |
129 | 1.Constructor类代表某个类中的一个构造方法 得到某个类所有的构造方法:
130 | 例子:
131 |
132 | ```java
133 | Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
134 | ```
135 | 得到某一个构造方法:
136 | 例子:
137 |
138 | ```java
139 | //获得方法时要用到类型
140 | Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
141 | ```
142 | 2.创建实例对象:
143 | 通常方式:
144 |
145 | ```java
146 | String str = new String(new StringBuffer("abc"));
147 | ```
148 | 反射方式:
149 |
150 | ```java
151 | String str = (String)constructor.newInstance(new StringBuffer("abc")); Class.newInstance()
152 | ```
153 | 方法: 例子:
154 |
155 | ```java
156 | String obj = (String)Class.forName("java.lang.String").newInstance();
157 | ```
158 | 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
159 |
160 | ### Field类
161 |
162 | `Field`类代表某个类中的一个成员变量
163 | 问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。(注意访问权限的问题)
164 | 示例代码:
165 |
166 | ```java
167 | ReflectPoint point = new ReflectPoint(1,7);
168 | Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");
169 | System.out.println(y.get(point)); //
170 | Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");
171 | Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x"); x.setAccessible(true);
172 | System.out.println(x.get(point));
173 | ```
174 | ### Method类
175 | Method类代表某个类中的一个成员方法 得到类中的某一个方法:
176 | 例子:
177 |
178 | ```java
179 | Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
180 | ```
181 | 调用方法:
182 | 通常方式:
183 |
184 | ```java
185 | System.out.println(str.charAt(1));
186 | ```
187 | 反射方式:
188 |
189 | ```java
190 | System.out.println(charAt.invoke(str, 1));
191 | ```
192 | 如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
193 | jdk1.4和jdk1.5的invoke方法的区别:
194 |
195 | Jdk1.5:public Object invoke(Object obj,Object... args)
196 | Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
197 |
198 | ### 用反射方式反射类中的main方法
199 |
200 | 目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊
201 | 问题:
202 | 启动Java程序的main方法的参数是一个字符串数组,即
203 |
204 | ```java
205 | public static void main(String[] args),
206 | ```
207 | 通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种
208 | 语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。 解决办法:
209 |
210 | ```java
211 | mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
212 | mainMethod.invoke(null,(Object)new String[]{"xxx"});
213 | ```
214 | 编译器会作特殊处理,编译时不把参数当作数组看待,也就
215 |
216 | ---
217 |
218 | # 9.内省技术
219 |
220 | ### 用内省技术反省javaBean
221 | 1.内省(Introspector) — JavaBean
222 | Introspector 类为通过工具学习有关受目标 Java Bean 支持的属性、事件和方法的知识提供了一个标准方法。
223 | 2.什么是JavaBean和属性的读写方法?
224 | 有get或set方法就是一个属性,另外所有类继承了Object类的getClass()方法,所以还有一个属性class。
225 | 3.访问JavaBean属性的两种方式:
226 | 1.直接调用bean的setXXX或getXXX方法。
227 | 2.通过内省技术访问(java.beans包提供了内省的API),内省技术访问也提供了两种方式。
228 | A.通过PropertyDescriptor类操作Bean的属性
229 | B.通过Introspector类获得Bean对象的BeanInfo,然后通过 BeanInfo 来获取属性的描述器( PropertyDescriptor ),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后通过反射机制来调用这些方法。
230 |
231 | ### 内省—beanutils工具包
232 |
233 | 1.Apache组织开发了一套用于操作JavaBean的API,这套API考虑到了很多实际开发中的应用场景,因此在实际开发中很多程序员使用这套API操作JavaBean,以简化程序代码的编写。
234 | 在工程下新建lib目录,导入commons-beanutils-1.8.3.jar 和支持包commons-logging-1.1.1.jar 选中两个包,右键build path/add to build path
235 | 2.Beanutils工具包的常用类: BeanUtils PropertyUtils
236 | ConvertUtils.regsiter(Converter convert, Class clazz)
237 |
238 | ---
239 |
240 | # 泛型技术(Generic)
241 |
242 | ### 泛形的作用
243 | 1.JDK5以前,对象保存到集合中就会失去其特性,取出时通常要程序员手工进行类型的强制 转换,这样不可避免就会引发程序的一些安全性问题。例如:
244 |
245 | ```java
246 | ArrayList list = new ArrayList(); list.add("abc");
247 | Integer num = (Integer) list.get(0); //运行时会出错,但编码时发现不了
248 | list.add(new Random()); list.add(new ArrayList());
249 | for(int i=0;i)
250 | ```
251 | 2.JDK5中的泛形允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定性(尤其在大型程序中更为突出)。
252 | 注意:泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。 泛形的基本术语,以ArrayList为例:<>念着typeof ArrayList中的E称为类型参数变量
253 | ArrayList中的Integer称为实际类型参数 整个称为ArrayList泛型类型
254 | 整个ArrayList称为参数化的类型ParameterizedType
255 |
256 | ### 泛型典型应用
257 |
258 | 使用迭代器迭代泛形集合中的元素。
259 | 使用增强for循环迭代泛形集合中的元素。 存取HashMap中的元素。 使用泛形时的几个常见问题:
260 | 使用泛形时,泛形类型须为引用类型,不能是基本数据类型 //使用泛型时,如果两边都使用到泛型,两边必须一样
261 |
262 | ```java
263 | ArrayList list = new ArrayList(); //bad
264 | ArrayList list = new ArrayList(); //bad
265 | ArrayList list = new ArrayList ();//ok
266 | ArrayList list = new ArrayList();//ok
267 | ```
268 | ### 自定义泛形——泛型方法
269 |
270 | Java程序中的普通方法、构造方法和静态方法中都可以使用泛型。方法使用泛形前,必须对泛形进行声明,语法:,T可以是任意字母,但通常必须要大写。通常需放在方法的返回值声明之前。例如:
271 |
272 | ```java
273 | public static void doxx(T t);
274 | ```
275 | 练习:
276 | 编写一个泛形方法,实现数组指定位置上元素的交换。
277 | 编写一个泛形方法,接收一个任意数组,并颠倒数组中的所有元素。 注意:
278 | 只有对象类型才能作为泛型方法的实际参数。 在泛型中可以同时有多个类型,例如:
279 |
280 | ```java
281 | public static V getValue(K key) { return map.get(key);}
282 | ```
283 | ### 自定义泛形——泛型类
284 |
285 | 如果一个类多处都要用到同一个泛型,可以把泛形定义在类上(即类级别的泛型),语法格式如下:
286 |
287 | ```java
288 | public class GenericDao {
289 | private T field1;
290 | public void save(T obj){}
291 | public T getId(int id){}
292 | }
293 | ```
294 | 注意,静态方法不能使用类定义的泛形,而应单独定义泛形。
295 |
296 | ### 泛型的高级应用——通配符
297 |
298 | 定义一个方法,接收一个集合,并打印出集合中的所有元素,如下所示:
299 |
300 | ```java
301 | void print (Collection c) {
302 | for (String e : c) {
303 | System.out.println(e);
304 | }
305 | }
306 | ```
307 | 问题:该方法只能打印保存了Object对象的集合,不能打印其它集合。通配符用于解决此类问题,方法的定义可改写为如下形式:
308 |
309 | ```java
310 | void print (Collection> c) { //Collection>(发音为:"collection of unknown")
311 | for (Object e : c) {
312 | System.out.println(e);
313 | }
314 | }
315 | ```
316 |
317 | 此种形式下需要注意的是:由于print方法c参数的类型为Collection>,即表示一种不确定的类型,因此在方法体内不能调用与类型相关的方法,例如add()方法。
318 | 总结:使用?通配符主要用于引用对象,使用了?通配符,就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
319 |
320 | ### 泛型的高级应用——有限制的通配符
321 | 限定通配符的上边界:
322 | 正确:
323 |
324 | ```java
325 | Vector x = new Vector();
326 | ```
327 | 错误:
328 | ```java
329 | Vector x = new Vector();
330 | ```
331 | 限定通配符的下边界:
332 | 正确:
333 |
334 | ```java
335 | Vector x = new Vector();
336 | ```
337 | 错误:
338 |
339 | ```java
340 | Vector x = new Vector();
341 | ```
342 | 问题:以下代码行不行?
343 |
344 | ```java
345 | public void add(List extends String> list){ list.add("abc"); }
346 | ```
347 | # 11.Annotation(注解) 概述
348 |
349 | 从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解)。 什么是Annotation,以及注解的作用?三个基本的 Annotation: @Override: 限定重写父类方法, 该注解只能用于方法 @Deprecated: 用于表示某个程序元素(类, 方法等)已过时 @SuppressWarnings: 抑制编译器警告.
350 | Annotation 其实就是代码里的特殊标记, 在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。 掌握注解技术的要点: 如何定义注解
351 | 如何反射注解
352 |
353 | ### 自定义Annotation
354 |
355 | 定义新的 Annotation 类型使用 @interface 关键字 声明注解的属性:
356 | Annotation 的属性声明方式:String name(); Annotation 属性默认值声明方式: String name() default “xxx”;
357 | 特殊属性value:如果注解中有一个名称为value的属性,那么使用注解时,可以省略value=部分,例如:@MyAnnotation(“xxx")。
358 |
359 | ### JDK的Annotation
360 |
361 | Annotation指修饰Annotation的Annotation。JDK中定义了如下元Annotation:
362 | @Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留的域, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 通过这个变量指定域。
363 | RetentionPolicy.CLASS: 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注释. 这是默认值
364 | RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释
365 | RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
366 | @Target: 指定注解用于修饰类的哪个成员. @Target 包含了一个名为 value 的成员变量.
367 | @Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档.
368 | @Inherited: 被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解
369 |
370 | ### 提取Annotation信息
371 |
372 | JDK 5.0 在 java.lang.reflect 包下新增了 AnnotationElement 接口, 该接口代表程序中可以接受注释的程序元素 当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取
373 | 程序可以调用 AnnotationElement 对象的如下方法来访问 Annotation 信息
374 |
375 | ### Tip:动态代理
376 |
377 | 在java里,每个对象都有一个类与之对应。
378 | 现在要生成某一个对象的代理对象,这个代理对象也要通过一个类来生成,所以首先要编写用于生成代理对象的类。
379 | 如何编写生成代理对象的类,两个要素: 代理谁
380 | 如何生成代理对象 代理谁?
381 | 设计一个类变量,以及一个构造函数,记住代理类代理哪个对象。 如何生成代理对象?
382 | 设计一个方法生成代理对象(在方法内编写代码生成代理对象是此处编程的难点) Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:
383 |
384 | 1.生成代理对象使用哪个类装载器
385 | 2.生成哪个对象的代理对象,通过接口指定
386 | 3.生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。 初学者必须理解,或不理解必须记住的2件事情:
387 | Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
388 | 由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。
389 |
390 | ### Tip:动态代理应用
391 |
392 | 在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。
393 | 并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。
394 |
395 | ### 类加载器
396 |
397 | 类加载器负责将 .class 文件(可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的 java.lang.Class 对象
398 | 当 JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构:
399 |
400 | ### bootstrap classloader
401 | bootstrap classloader:引导(也称为原始)类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
402 |
403 | ```java
404 | URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); }
405 | ```
406 | 因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库
407 |
408 | ### extension classloader
409 |
410 | extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中的JAR包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。
411 |
412 | ### system classloader
413 |
414 | system classloader -系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。
415 | 可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。
416 |
417 | ### 全盘负责委托机制
418 |
419 | classloader 加载类用的是全盘负责委托机制。
420 | 全盘负责:即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。
421 | 委托机制:先让parent(父)类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
422 | 类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。 HttpUrlConnection
423 | 不用myeclipse怎么去开发web工程
424 |
425 | ### Tip:DTD 的语法细节:元素定义1
426 |
427 | 在DTD文档中使用ELEMENT声明一个XML元素,语法格式如下所示: 元素类型可以是元素内容、或类型
428 | 如为元素内容:则需要使用()括起来,如
429 | 如为元素类型,则直接书写,DTD规范定义了如下几种类型: EMPTY:用于定义空元素,例如
430 |
431 | ANY:表示元素内容为任意类型。
432 | 元素内容中可以使用如下方式,描述内容的组成关系
433 | 元素内容使用空白符分隔,表示出现顺序没有要求: × 用逗号分隔,表示内容的出现顺序必须与声明时一致。 用|分隔,表示任选其一,即多个只能出现一个
434 | 在元素内容中也可以使用+、*、?等符号表示元素出现的次数: +: 一次或多次 (书+) ?: 0次或一次 (书?) *: 0次或多次 (书*) 也可使用圆括号( )批量设置,例
435 |
436 | ### Tip:属性定义
437 |
438 | xml文档中的标签属性需通过ATTLIST为其设置属性 语法格式:
439 | 属性声明举例:
440 | 对应XML文件: „ „ 设置说明:
441 | #REQUIRED:必须设置该属性
442 | #IMPLIED:可以设置也可以不设置
443 | #FIXED:说明该属性的取值固定为一个值,在 XML 文件中不能为该属性设置其它值。但需要为该属性提供这个值
444 | 直接使用默认值:在 XML 中可以设置该值也可以不设置该属性值。若没设置则使用默认值。 举例:
445 |
446 | ### Tip:常用属性值类型
447 | CDATA:表示属性值为普通文本字符串。 ENUMERATED ID
448 | ENTITY(实体)
449 |
450 | ### Tip:属性值类型
451 |
452 | 属性的类型可以是一组取值的列表,在 XML 文件中设置的属性值只能是这个列表中的某个值(枚举)
453 |
454 | ```xml
455 | ]>
456 | ```
457 | ### Tip:实体定义
458 | 实体用于为一段内容创建一个别名,以后在XML文档中就可以使用别名引用这段内容了。 在DTD定义中,一条语句用于定义一个实体。
459 | 实体可分为两种类型:引用实体和参数实体。
460 |
461 | ### Tip:实体定义引用实体
462 | 引用实体主要在 XML 文档中被应用 语法格式:
463 | :直接转变成实体内容 引用方式: &实体名称; 举例: …… ©right;
464 |
465 | ### Tip:实体定义参数实体
466 | 参数实体被 DTD 文件自身使用 语法格式:
467 | 引用方式: %实体名称; 举例1:
468 | 举例2:
469 | ...
470 |
471 | ### Tip:XML解析技术概述
472 |
473 | XML解析方式分为两种:dom和sax
474 | dom:(Document Object Model, 即文档对象模型) 是 W3C 组织推荐的处理 XML 的一种方式。
475 | sax: (Simple API for XML) 不是官方标准,但它是 XML 社区事实上的标准,几乎所有的 XML 解析器都支持它。 XML解析器
476 | Crimson、Xerces 、Aelfred2 XML解析开发包 Jaxp、Jdom、dom4j
477 |
478 | ### Tip:JAXP
479 |
480 | JAXP 开发包是J2SE的一部分,它由javax.xml、org.w3c.dom 、org.xml.sax 包及其子包组成 在 javax.xml.parsers 包中,定义了几个工厂类,程序员调用这些工厂类,可以得到对xml文档进行解析的 DOM 或
481 | SAX 的解析器对象。
482 |
483 | ### Tip:使用JAXP进行DOM解析
484 |
485 | javax.xml.parsers 包中的DocumentBuilderFactory用于创建DOM模式的解析器对象 , DocumentBuilderFactory是一个抽象工厂类,它不能直接实例化,但该类提供了一个newInstance方法 ,这个方法会根据本地平台默认安装的解析器,自动创建一个工厂的对象并返回。
486 |
487 | ### Tip:获得JAXP中的DOM解析器
488 |
489 | 调用 DocumentBuilderFactory.newInstance() 方法得到创建 DOM 解析器的工厂。
490 | 调用工厂对象的 newDocumentBuilder方法得到 DOM 解析器对象。
491 | 调用 DOM 解析器对象的 parse() 方法解析 XML 文档,得到代表整个文档的 Document 对象,进行可以利用DOM
492 |
493 | ### 调虚拟机内存大小
494 |
495 | -Xmx83886080 C:\Program Files\Java\jdk1.7.0\docs\technotes\tools\windows\java.html -Xmx81920k -Xmx80m
496 |
497 | ### Tip:DOM编程
498 | DOM模型(document object model)
499 | DOM解析器在解析XML文档时,会把文档中的所有元素,按照其出现的层次关系,解析成一个个Node对象(节点)。
500 | 在dom中,节点之间关系如下:
501 | 位于一个节点之上的节点是该节点的父节点(parent) 一个节点之下的节点是该节点的子节点(children)
502 | 同一层次,具有相同父节点的节点是兄弟节点(sibling) 一个节点的下一个层次的节点集合是节点后代(descendant)
503 | 父、祖父节点及所有位于节点上面的,都是节点的祖先(ancestor) 节点类型(下页ppt)
504 | Node对象提供了一系列常量来代表结点的类型,当开发人员获得某个Node类型后,就可以把Node节点转换成相应的节点对象(Node的子类对象),以便于调用其特有的方法。(查看API文档)
505 | Node对象提供了相应的方法去获得它的父结点或子结点。编程人员通过这些方法就可以读取整个XML文档的内容、或添加、修改、删除XML文档的内容了。
506 |
507 | ### Tip:DOM方式解析XML文件
508 | DOM解析编程 遍历所有节点 查找某一个节点 删除结点 更新结点 添加节点
509 |
510 | ### Tip:更新XML文档
511 | javax.xml.transform包中的Transformer类用于把代表XML文件的Document对象转换为某种格式后进行输出,例如把xml文件应用样式表后转成一个html文档。利用这个对象,当然也可以把Document对象又重新写入到一个XML文件中。
512 | Transformer类通过transform方法完成转换操作,该方法接收一个源和一个目的地。我们可以通过: javax.xml.transform.dom.DOMSource类来关联要转换的document对象, 用javax.xml.transform.stream.StreamResult 对象来表示数据的目的地。 Transformer对象通过TransformerFactory获得。
513 |
514 | ### Tip:SAX解析
515 | 在使用 DOM 解析 XML 文档时,需要读取整个 XML 文档,在内存中构架代表整个 DOM 树的Doucment对象,从而再对XML文档进行操作。此种情况下,如果 XML 文档特别大,就会消耗计算机的大量内存,并且容易导致内存溢出。
516 | SAX解析允许在读取文档的时候,即对文档进行处理,而不必等到整个文档装载完才会文档进行操作。
517 | SAX采用事件处理的方式解析XML文件,利用 SAX 解析 XML 文档,涉及两个部分:解析器和事件处理器: 解析器可以使用JAXP的API创建,创建出SAX解析器后,就可以指定解析器去解析某个XML文档。
518 | 解析器采用SAX方式在解析某个XML文档时,它只要解析到XML文档的一个组成部分,都会去调用事件处理器的一个方法,解析器在调用事件处理器的方法时,会把当前解析到的xml文件内容作为方法的参数传递给事件处理器。
519 | 事件处理器由程序员编写,程序员通过事件处理器中方法的参数,就可以很轻松地得到sax解析器解析到的数据,从而可以决定如何对数据进行处理。
520 | 阅读ContentHandler API文档,常用方法:startElement、endElement、characters
521 |
522 | ### Tip:SAX方式解析XML文档
523 |
524 | 使用SAXParserFactory创建SAX解析工厂
525 | SAXParserFactory spf = SAXParserFactory.newInstance(); 通过SAX解析工厂得到解析器对象 SAXParser sp = spf.newSAXParser();
526 | 通过解析器对象得到一个XML的读取器 XMLReader xmlReader = sp.getXMLReader();
527 | 设置读取器的事件处理器
528 | xmlReader.setContentHandler(new BookParserHandler()); 解析xml文件
529 |
530 | ---
531 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
532 | - Good Luck!
533 |
--------------------------------------------------------------------------------
/Android进阶/MaterialDesign使用(二).md:
--------------------------------------------------------------------------------
1 | MaterialDesign使用(二)
2 | ===
3 |
4 | ###MaterialList库的运用
5 |
6 | >简介:[MaterialList](https://github.com/dexafree/MaterialList)是一个能够帮助所有`Android`开发者获取谷歌UI设计规范中新增的`CardView`(卡片视图)的开源库,支持`Android 2.3+`系统。作为`ListView`的扩展,`MaterialList`可以接收、存储卡片列表,并根据它们的`Android`风格和设计模式进行展示。此外,开发者还可以创建专属于自己的卡片布局,并轻松将其添加到`CardList`中。
7 |
8 |
9 |
10 | - 配置`build.gradle`
11 |
12 | ```java
13 | dependencies {
14 | ...
15 | compile 'com.github.dexafree:materiallist:3.1.3'
16 | }
17 | ```
18 | - Step1:在布局文件中声明一个`MaterialListVie`
19 |
20 | ```xml
21 |
28 |
29 |
33 |
34 |
35 | ```
36 | - Step2: 获取`MaterialListView`实例
37 |
38 | ```java
39 | MaterialListView mListView = (MaterialListView) findViewById(R.id.material_listview);
40 | ```
41 |
42 | - Step3:往`MaterialListView`里添加`Cards`
43 |
44 | ```java
45 | Card card = new Card.Builder(this)
46 | .setTag("BASIC_IMAGE_BUTTONS_CARD")
47 | .withProvider(BasicImageButtonsCardProvider.class)
48 | .setTitle("I'm new")
49 | .setDescription("I've been generated on runtime!")
50 | .setDrawable(R.drawable.dog)
51 | .endConfig()
52 | .build()
53 |
54 | mListView.add(card);
55 | ```
56 | - 实例:
57 |
58 | ```java
59 | public class MainActivity extends AppCompatActivity {
60 | private Context mContext;
61 | private MaterialListView mListView;
62 |
63 | @Override
64 | protected void onCreate(Bundle savedInstanceState) {
65 | super.onCreate(savedInstanceState);
66 | setContentView(R.layout.activity_main);
67 |
68 | // Save a reference to the context
69 | mContext = this;
70 |
71 | // Bind the MaterialListView to a variable
72 | mListView = (MaterialListView) findViewById(R.id.material_listview);
73 | mListView.setItemAnimator(new SlideInLeftAnimator());
74 | mListView.getItemAnimator().setAddDuration(300);
75 | mListView.getItemAnimator().setRemoveDuration(300);
76 |
77 | final ImageView emptyView = (ImageView) findViewById(R.id.imageView);
78 | emptyView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
79 | mListView.setEmptyView(emptyView);
80 | Picasso.with(this)
81 | .load("https://www.skyverge.com/wp-content/uploads/2012/05/github-logo.png")
82 | .resize(100, 100)
83 | .centerInside()
84 | .into(emptyView);
85 |
86 | // Fill the array withProvider mock content
87 | fillArray();
88 |
89 | // Set the dismiss listener
90 | mListView.setOnDismissCallback(new OnDismissCallback() {
91 | @Override
92 | public void onDismiss(@NonNull Card card, int position) {
93 | // Show a toast
94 | Toast.makeText(mContext, "You have dismissed a " + card.getTag(), Toast.LENGTH_SHORT).show();
95 | }
96 | });
97 |
98 | // Add the ItemTouchListener
99 | mListView.addOnItemTouchListener(new RecyclerItemClickListener.OnItemClickListener() {
100 | @Override
101 | public void onItemClick(@NonNull Card card, int position) {
102 | Log.d("CARD_TYPE", "" + card.getTag());
103 | }
104 |
105 | @Override
106 | public void onItemLongClick(@NonNull Card card, int position) {
107 | Log.d("LONG_CLICK", "" + card.getTag());
108 | }
109 | });
110 |
111 | }
112 |
113 | private void fillArray() {
114 | List cards = new ArrayList<>();
115 | for (int i = 0; i < 35; i++) {
116 | cards.add(getRandomCard(i));
117 | }
118 | mListView.getAdapter().addAll(cards);
119 | }
120 |
121 | private Card getRandomCard(final int position) {
122 | String title = "Card number " + (position + 1);
123 | String description = "Lorem ipsum dolor sit amet";
124 |
125 | switch (position % 7) {
126 | case 0: {
127 | return new Card.Builder(this)
128 | .setTag("SMALL_IMAGE_CARD")
129 | .setDismissible()
130 | .withProvider(new CardProvider())
131 | .setLayout(R.layout.material_small_image_card)
132 | .setTitle(title)
133 | .setDescription(description)
134 | .setDrawable(R.drawable.sample_android)
135 | .setDrawableConfiguration(new CardProvider.OnImageConfigListener() {
136 | @Override
137 | public void onImageConfigure(@NonNull final RequestCreator requestCreator) {
138 | requestCreator.rotate(position * 90.0f)
139 | .resize(150, 150)
140 | .centerCrop();
141 | }
142 | })
143 | .endConfig()
144 | .build();
145 | }
146 | case 1: {
147 | return new Card.Builder(this)
148 | .setTag("BIG_IMAGE_CARD")
149 | .withProvider(new CardProvider())
150 | .setLayout(R.layout.material_big_image_card_layout)
151 | .setTitle(title)
152 | .setSubtitle(description)
153 | .setSubtitleGravity(Gravity.END)
154 | .setDrawable("https://assets-cdn.github.com/images/modules/logos_page/GitHub-Mark.png")
155 | .setDrawableConfiguration(new CardProvider.OnImageConfigListener() {
156 | @Override
157 | public void onImageConfigure(@NonNull final RequestCreator requestCreator) {
158 | requestCreator.rotate(position * 45.0f)
159 | .resize(200, 200)
160 | .centerCrop();
161 | }
162 | })
163 | .endConfig()
164 | .build();
165 | }
166 | case 2: {
167 | final CardProvider provider = new Card.Builder(this)
168 | .setTag("BASIC_IMAGE_BUTTON_CARD")
169 | .setDismissible()
170 | .withProvider(new CardProvider<>())
171 | .setLayout(R.layout.material_basic_image_buttons_card_layout)
172 | .setTitle(title)
173 | .setTitleGravity(Gravity.END)
174 | .setDescription(description)
175 | .setDescriptionGravity(Gravity.END)
176 | .setDrawable(R.drawable.dog)
177 | .setDrawableConfiguration(new CardProvider.OnImageConfigListener() {
178 | @Override
179 | public void onImageConfigure(@NonNull RequestCreator requestCreator) {
180 | requestCreator.fit();
181 | }
182 | })
183 | .addAction(R.id.left_text_button, new TextViewAction(this)
184 | .setText("left")
185 | .setTextResourceColor(R.color.black_button)
186 | .setListener(new OnActionClickListener() {
187 | @Override
188 | public void onActionClicked(View view, Card card) {
189 | Toast.makeText(mContext, "You have pressed the left button", Toast.LENGTH_SHORT).show();
190 | card.getProvider().setTitle("CHANGED ON RUNTIME");
191 | }
192 | }))
193 | .addAction(R.id.right_text_button, new TextViewAction(this)
194 | .setText("right")
195 | .setTextResourceColor(R.color.orange_button)
196 | .setListener(new OnActionClickListener() {
197 | @Override
198 | public void onActionClicked(View view, Card card) {
199 | Toast.makeText(mContext, "You have pressed the right button on card " + card.getProvider().getTitle(), Toast.LENGTH_SHORT).show();
200 | card.dismiss();
201 | }
202 | }));
203 |
204 | if (position % 2 == 0) {
205 | provider.setDividerVisible(true);
206 | }
207 |
208 | return provider.endConfig().build();
209 | }
210 | case 3: {
211 | final CardProvider provider = new Card.Builder(this)
212 | .setTag("BASIC_BUTTONS_CARD")
213 | .setDismissible()
214 | .withProvider(new CardProvider())
215 | .setLayout(R.layout.material_basic_buttons_card)
216 | .setTitle(title)
217 | .setDescription(description)
218 | .addAction(R.id.left_text_button, new TextViewAction(this)
219 | .setText("left")
220 | .setTextResourceColor(R.color.black_button)
221 | .setListener(new OnActionClickListener() {
222 | @Override
223 | public void onActionClicked(View view, Card card) {
224 | Toast.makeText(mContext, "You have pressed the left button", Toast.LENGTH_SHORT).show();
225 | }
226 | }))
227 | .addAction(R.id.right_text_button, new TextViewAction(this)
228 | .setText("right")
229 | .setTextResourceColor(R.color.accent_material_dark)
230 | .setListener(new OnActionClickListener() {
231 | @Override
232 | public void onActionClicked(View view, Card card) {
233 | Toast.makeText(mContext, "You have pressed the right button", Toast.LENGTH_SHORT).show();
234 | }
235 | }));
236 |
237 | if (position % 2 == 0) {
238 | provider.setDividerVisible(true);
239 | }
240 |
241 | return provider.endConfig().build();
242 | }
243 | case 4: {
244 | final CardProvider provider = new Card.Builder(this)
245 | .setTag("WELCOME_CARD")
246 | .setDismissible()
247 | .withProvider(new CardProvider())
248 | .setLayout(R.layout.material_welcome_card_layout)
249 | .setTitle("Welcome Card")
250 | .setTitleColor(Color.WHITE)
251 | .setDescription("I am the description")
252 | .setDescriptionColor(Color.WHITE)
253 | .setSubtitle("My subtitle!")
254 | .setSubtitleColor(Color.WHITE)
255 | .setBackgroundColor(Color.BLUE)
256 | .addAction(R.id.ok_button, new WelcomeButtonAction(this)
257 | .setText("Okay!")
258 | .setTextColor(Color.WHITE)
259 | .setListener(new OnActionClickListener() {
260 | @Override
261 | public void onActionClicked(View view, Card card) {
262 | Toast.makeText(mContext, "Welcome!", Toast.LENGTH_SHORT).show();
263 | }
264 | }));
265 |
266 | if (position % 2 == 0) {
267 | provider.setBackgroundResourceColor(android.R.color.background_dark);
268 | }
269 |
270 | return provider.endConfig().build();
271 | }
272 | case 5: {
273 | ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
274 | adapter.add("Hello");
275 | adapter.add("World");
276 | adapter.add("!");
277 |
278 | return new Card.Builder(this)
279 | .setTag("LIST_CARD")
280 | .setDismissible()
281 | .withProvider(new ListCardProvider())
282 | .setLayout(R.layout.material_list_card_layout)
283 | .setTitle("List Card")
284 | .setDescription("Take a list")
285 | .setAdapter(adapter)
286 | .endConfig()
287 | .build();
288 | }
289 | default: {
290 | final CardProvider provider = new Card.Builder(this)
291 | .setTag("BIG_IMAGE_BUTTONS_CARD")
292 | .setDismissible()
293 | .withProvider(new CardProvider())
294 | .setLayout(R.layout.material_image_with_buttons_card)
295 | .setTitle(title)
296 | .setDescription(description)
297 | .setDrawable(R.drawable.photo)
298 | .addAction(R.id.left_text_button, new TextViewAction(this)
299 | .setText("add card")
300 | .setTextResourceColor(R.color.black_button)
301 | .setListener(new OnActionClickListener() {
302 | @Override
303 | public void onActionClicked(View view, Card card) {
304 | Log.d("ADDING", "CARD");
305 |
306 | mListView.getAdapter().add(generateNewCard());
307 | Toast.makeText(mContext, "Added new card", Toast.LENGTH_SHORT).show();
308 | }
309 | }))
310 | .addAction(R.id.right_text_button, new TextViewAction(this)
311 | .setText("right button")
312 | .setTextResourceColor(R.color.accent_material_dark)
313 | .setListener(new OnActionClickListener() {
314 | @Override
315 | public void onActionClicked(View view, Card card) {
316 | Toast.makeText(mContext, "You have pressed the right button", Toast.LENGTH_SHORT).show();
317 | }
318 | }));
319 |
320 | if (position % 2 == 0) {
321 | provider.setDividerVisible(true);
322 | }
323 |
324 | return provider.endConfig().build();
325 | }
326 | }
327 | }
328 |
329 | private Card generateNewCard() {
330 | return new Card.Builder(this)
331 | .setTag("BASIC_IMAGE_BUTTONS_CARD")
332 | .withProvider(new CardProvider())
333 | .setLayout(R.layout.material_basic_image_buttons_card_layout)
334 | .setTitle("I'm new")
335 | .setDescription("I've been generated on runtime!")
336 | .setDrawable(R.drawable.dog)
337 | .endConfig()
338 | .build();
339 | }
340 |
341 | private void addMockCardAtStart() {
342 | mListView.getAdapter().addAtStart(new Card.Builder(this)
343 | .setTag("BASIC_IMAGE_BUTTONS_CARD")
344 | .setDismissible()
345 | .withProvider(new CardProvider())
346 | .setLayout(R.layout.material_basic_image_buttons_card_layout)
347 | .setTitle("Hi there")
348 | .setDescription("I've been added on top!")
349 | .addAction(R.id.left_text_button, new TextViewAction(this)
350 | .setText("left")
351 | .setTextResourceColor(R.color.black_button))
352 | .addAction(R.id.right_text_button, new TextViewAction(this)
353 | .setText("right")
354 | .setTextResourceColor(R.color.orange_button))
355 | .setDrawable(R.drawable.dog)
356 | .endConfig()
357 | .build());
358 | }
359 |
360 | @Override
361 | public boolean onCreateOptionsMenu(Menu menu) {
362 | getMenuInflater().inflate(R.menu.main, menu);
363 | return true;
364 | }
365 |
366 | @Override
367 | public boolean onOptionsItemSelected(MenuItem item) {
368 | switch (item.getItemId()) {
369 | case R.id.action_clear:
370 | mListView.getAdapter().clearAll();
371 | break;
372 | case R.id.action_add_at_start:
373 | addMockCardAtStart();
374 | break;
375 | }
376 | return super.onOptionsItemSelected(item);
377 | }
378 | }
379 | ```
380 | ---
381 | - 邮箱 :elisabethzhen@163.com
382 | - Good Luck!
383 |
--------------------------------------------------------------------------------
/Android进阶/开源库的基本使用/Dagger-依赖注入入门.md:
--------------------------------------------------------------------------------
1 | # Dagger入门知识归纳
2 | ### 依赖注入(Dependency Injection)简介
3 |
4 | 在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。而控制反转用于解耦,控制反转还有一种常见的实现方式称为依赖查找。
5 |
6 | 使用依赖注入可以带来以下好处:
7 |
8 | - 依赖的注入和配置独立于组件之外。
9 | 因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。
10 |
11 | - 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。
12 | 可以看到,能够管理创建实例的范围是一件非常棒的事情。按我的观点,你app中的所有对象或者协作者都不应该知道有关实例创建和生命周期的任何事情,这些都应该由我们的依赖注入框架管理的。
13 |
14 | 
15 |
16 | 引用一段例子来介绍为什么需要依赖注入
17 |
18 | ```java
19 | public class MovieLister {
20 | private MovieFinder finder;
21 |
22 | public MovieLister() {
23 | finder = new MovieFinderImpl();
24 | }
25 |
26 | public Movie[] moviesDirectedBy(String arg) {
27 | List allMovies = finder.findAll();
28 | for (Iterator it = allMovies.iterator(); it.hasNext();) {
29 | Movie movie = (Movie) it.next();
30 | if (!movie.getDirector().equals(arg)) it.remove();
31 | }
32 | return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
33 | }
34 | ...
35 | }
36 |
37 | public interface MovieFinder {
38 | List findAll();
39 | }
40 | ```
41 |
42 | > 引用[CodeThink](http://codethink.me/2015/08/01/dependency-injection-theory/)中对这段代码的解释:
43 |
44 | >我们创建了一个名为`MovieLister`的类来提供需要的电影列表,它`moviesDirectedBy`方法提供根据导演名来搜索电影的方式。真正负责搜索电影的是实现了`MovieFinder`接口的`MovieFinderImpl`,我们的`MovieLister`类在构造函数中创建了一个`MovieFinderImpl`的对象。
45 |
46 | >目前看来,一切都不错。但是,当我们希望修改`finder`,将`finder`替换为一种新的实现时(比如为`MovieFinder`增加一个参数表明`Movie`数据的来源是哪个数据库),我们不仅需要修改`MovieFinderImpl`类,还需要修改我们`MovieLister`中创建`MovieFinderImpl`的代码。
47 |
48 | >这就是依赖注入要处理的耦合。这种在`MovieLister`中创建`MovieFinderImpl`的方式,使得`MovieLister`不仅仅依赖于`MovieFinder`这个接口,它还依赖于`MovieListImpl`这个实现。 这种在一个类中直接创建另一个类的对象的代码,和硬编码(hard-coded strings)以及硬编码的数字(magic numbers)一样,是一种导致耦合的坏味道,我们可以把这种坏味道称为硬初始化(hard init)。同时,我们也应该像记住硬编码一样记住,new(对象创建)是有毒的。
49 | >Hard Init带来的主要坏处有两个方面:1)上文所述的修改其实现时,需要修改创建处的代码;2)不便于测试,这种方式创建的类(上文中的`MovieLister`)无法单独被测试,其行为和`MovieFinderImpl`紧紧耦合在一起,同时,也会导致代码的可读性问题(“如果一段代码不便于测试,那么它一定不便于阅读。”)。
50 |
51 |
52 | ---
53 | ### 依赖注入的实现方式
54 |
55 | 1. 构造函数注入(Contructor Injection)
56 |
57 | 修改一下上面代码中`MovieList`的构造函数,使得`MovieFinderImpl`的实现在`MovieLister`类之外创建。这样,`MovieLister`就只依赖于我们定义的`MovieFinder`接口,而不依赖于`MovieFinder`的实现了。
58 |
59 | ```java
60 | public class MovieLister {
61 | private MovieFinder finder;
62 |
63 | public MovieLister(MovieFinder finder) {
64 | this.finder = finder;
65 | }
66 | ...
67 | }
68 | ```
69 | 2. `setter`注入
70 |
71 | 增加一个`setter`函数来传入创建好的`MovieFinder`对象,这样同样可以避免在`MovieFinder`中`hard init`这个对象
72 |
73 | ```java
74 | public class MovieLister {
75 | ...
76 | public void setFinder(MovieFinder finder) {
77 | this.finder = finder;
78 | }
79 | }
80 | ```
81 |
82 | 3. 接口注入
83 |
84 | 接口注入使用接口来提供`setter`方法,其实现方式如下:
85 |
86 | 首先要创建一个注入使用的接口
87 |
88 | ```java
89 | public interface InjectFinder {
90 | void injectFinder(MovieFinder finder);
91 | }
92 | ```
93 | 之后,让`MovieLister`实现这个接口。
94 |
95 | ```java
96 | class MovieLister implements InjectFinder {
97 | ...
98 | public void injectFinder(MovieFinder finder) {
99 | this.finder = finder;
100 | }
101 | ...
102 | }
103 | ```
104 | 最后,我们需要根据不同的框架创建被依赖的`MovieFinder`的实现。
105 |
106 | ---
107 | ### Dagger1
108 |
109 |
110 | 基本特点:
111 |
112 | - 多个注入点:依赖,通过injected
113 | - 多种绑定方法:依赖,通过provided
114 | - 多个modules:实现某种功能的绑定集合
115 | - 多个对象图: 实现一个范围的modules集合
116 |
117 | Dagger1是在编译的时候实行绑定,不过也用到了反射机制。但这个反射不是用来实例化对象的,而是用于图的构成。Dagger会在运行的时候去检测是否一切都正常工作,所以使用的时候会付出一些代价:偶尔会无效和调试困难。
118 |
119 | ---
120 | ### Dagger2
121 |
122 | `Dagger2`与`Dagger1`之间的区别:
123 |
124 | - 再也没有使用反射:图的验证、配置和预先设置都在编译的时候执行。
125 | - 容易调试和可跟踪:完全具体地调用提供和创建的堆栈
126 | - 更好的性能:谷歌声称他们提高了13%的处理性能
127 | - 代码混淆:使用派遣方法,就如同自己写的代码一样
128 |
129 | 当然所有这些很棒的特点都需要付出一个代价,那就是缺乏灵活性,例如:Dagger2没用反射所以没有动态机制。
130 |
131 | #### 深入研究
132 |
133 | 一些依赖注入的基本概念:
134 |
135 | - `@Inject`: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉`Dagger`这个类或者字段需要依赖注入。这样,`Dagger`就会构造一个这个类的实例并满足他们的依赖。
136 | - `@Module`: `Modules`类里面的方法专门提供依赖,所以我们定义一个类,用`@Module`注解,这样`Dagger`在构造类的实例的时候,就知道从哪里去找到需要的 依赖。`modules`的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的`modules`)。
137 | - `@Provide`: 在`modules`中,我们定义的方法是用这个注解,以此来告诉`Dagger`我们想要构造对象并提供这些依赖。
138 | - `@Component`: `Components`从根本上来说就是一个**注入器**,也可以说是`@Inject`和`@Module`的桥梁,**它的主要作用就是连接这两个部分**。 `Components`可以提供所有定义了的类型的实例,比如:我们必须用`@Component`注解一个接口然后列出所有的`@Modules`组成该组件,如果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的`modules`知道依赖的范围。
139 | - `@Scope`: `Scopes`可是非常的有用,`Dagger2`可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没必要让每个对象都去了解如何管理他们的实例。在`scope`的例子中,我们用自定义的`@PerActivity`注解一个类,所以这个对象存活时间就和`activity`的一样。简单来说就是我们可以定义所有范围的粒度(`@PerFragment`, `@PerUser`, 等等)。
140 | - `Qualifier`: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在`Android`中,我们会需要不同类型的`context`,所以我们就可以定义`qualifier`注解`“@ForApplication”`和`“@ForActivity”`,这样当注入一个`context`的时候,我们就可以告诉 `Dagger`我们想要哪种类型的`context`。
141 |
142 | #### `build.gradle`文件配置:
143 |
144 | ```java
145 | apply plugin: 'com.neenbedankt.android-apt'
146 |
147 | buildscript {
148 | repositories {
149 | jcenter()
150 | }
151 | dependencies {
152 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
153 | }
154 | }
155 |
156 | android {
157 | ...
158 | }
159 |
160 | dependencies {
161 | apt 'com.google.dagger:dagger-compiler:2.0'
162 | compile 'com.google.dagger:dagger:2.0'
163 |
164 | ...
165 | }
166 | ```
167 | 添加了编译和运行库,还有必不可少的`apt`插件,没有这插件,`dagger`可能不会正常工作,特别是在`Android studio`中。
168 |
169 | #### 例子
170 |
171 | ```java
172 | @Override void initializePresenter() {
173 | // All this dependency initialization could have been avoided by using a
174 | // dependency injection framework. But in this case this is used this way for
175 | // LEARNING EXAMPLE PURPOSE.
176 | ThreadExecutor threadExecutor = JobExecutor.getInstance();
177 | PostExecutionThread postExecutionThread = UIThread.getInstance();
178 |
179 | JsonSerializer userCacheSerializer = new JsonSerializer();
180 | UserCache userCache = UserCacheImpl.getInstance(getActivity(), userCacheSerializer,
181 | FileManager.getInstance(), threadExecutor);
182 | UserDataStoreFactory userDataStoreFactory =
183 | new UserDataStoreFactory(this.getContext(), userCache);
184 | UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper();
185 | UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory,
186 | userEntityDataMapper);
187 |
188 | GetUserDetailsUseCase getUserDetailsUseCase = new GetUserDetailsUseCaseImpl(userRepository,
189 | threadExecutor, postExecutionThread);
190 | UserModelDataMapper userModelDataMapper = new UserModelDataMapper();
191 |
192 | this.userDetailsPresenter =
193 | new UserDetailsPresenter(this, getUserDetailsUseCase, userModelDataMapper);
194 | }
195 | ```
196 | 上面的问题的解决办法是使用依赖注入框架。我们要避免像上面这样引用代码:这个类不能涉及对象的创建和依赖的提供。 那我们该怎么做呢,当然是使用`Dagger2`,我们先看看结构图:
197 |
198 | 
199 |
200 | 接下来我们会分解这张图,并解释各个部分还有代码。
201 |
202 | - `Application Component`: 生命周期跟`Application`一样的组件。可注入到`AndroidApplication`和`BaseActivity`中类中。
203 |
204 | ```java
205 | @Singleton // Constraints this component to one-per-application or unscoped bindings.
206 | @Component(modules = ApplicationModule.class)
207 | public interface ApplicationComponent {
208 | void inject(BaseActivity baseActivity);
209 |
210 | //暴露给子图
211 | Context context();
212 | ThreadExecutor threadExecutor();
213 | PostExecutionThread postExecutionThread();
214 | UserRepository userRepository();
215 | }
216 | ```
217 | 为这个组件使用了`@Singleton`注解,使其保证唯一性。也许你会问为什么要将`context`和其他成员暴露出去。这正是`Dagger`中 `components`工作的重要性质:如果你不想把`modules`的类型暴露出来,那么你就只能显示地使用它们。在这个例子中,将这些元素暴露给子图, 如果你把他们删掉,编译的时候就会报错。
218 |
219 | - `Application Module`: `module`的作用是提供在应用的生命周期中存活的对象。这也是为什么`@Provide`注解的方法要用`@Singleton`限定。
220 |
221 | ```java
222 | @Module
223 | public class ApplicationModule {
224 | private final AndroidApplication application;
225 |
226 | public ApplicationModule(AndroidApplication application) {
227 | this.application = application;
228 | }
229 |
230 | @Provides @Singleton Context provideApplicationContext() {
231 | return this.application;
232 | }
233 |
234 | @Provides @Singleton Navigator provideNavigator() {
235 | return new Navigator();
236 | }
237 |
238 | @Provides @Singleton ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) {
239 | return jobExecutor;
240 | }
241 |
242 | @Provides @Singleton PostExecutionThread providePostExecutionThread(UIThread uiThread) {
243 | return uiThread;
244 | }
245 |
246 | @Provides @Singleton UserCache provideUserCache(UserCacheImpl userCache) {
247 | return userCache;
248 | }
249 |
250 | @Provides @Singleton UserRepository provideUserRepository(UserDataRepository userDataRepository) {
251 | return userDataRepository;
252 | }
253 | }
254 | ```
255 | - `Activity Component`: 生命周期跟`Activity`一样的组件。
256 |
257 | ```java
258 | @PerActivity
259 | @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
260 | public interface ActivityComponent {
261 | //Exposed to sub-graphs.
262 | Activity activity();
263 | }
264 | ```
265 | `@PerActivity`是一个自定义的范围注解,作用是允许对象被记录在正确的组件中,当然这些对象的生命周期应该遵循`activity`的生命周期。这是一个很好的练习,我建议你们都做一下,有以下好处:
266 |
267 | - 注入对象到构造方法需要的`activity`。
268 | - 在一个`per-activity`基础上的单例使用。
269 | - 只能在`activity`中使用使得全局的对象图保持清晰。
270 |
271 | 代码如下:
272 |
273 | ```java
274 | @Scope
275 | @Retention(RUNTIME)
276 | public @interface PerActivity {}
277 | ```
278 |
279 | - `Activity Module`: 在对象图中,这个`module`把`activity`暴露给相关联的类。比如在`fragment`中使用`activity`的`context`。
280 |
281 | ```java
282 | @Module
283 | public class ActivityModule {
284 | private final Activity activity;
285 |
286 | public ActivityModule(Activity activity) {
287 | this.activity = activity;
288 | }
289 |
290 | @Provides @PerActivity Activity activity() {
291 | return this.activity;
292 | }
293 | }
294 | ```
295 |
296 | - `User Component`: 继承于`ActivityComponent`的组件,并用`@PerActivity`注解。我通常会在注入用户相关的`fragment`中使用。因为 `ActivityModule`把`activity`暴露给图了,所以在任何需要一个`activity`的`context`的时候,`Dagger`都可以提供注入, 没必要再在子`modules`中定义了。
297 |
298 | ```java
299 | @PerActivity
300 | @Component(dependencies = ApplicationComponent.class, modules = {ActivityModule.class, UserModule.class})
301 | public interface UserComponent extends ActivityComponent {
302 | void inject(UserListFragment userListFragment);
303 | void inject(UserDetailsFragment userDetailsFragment);
304 | }
305 | ```
306 | - `User Module`: 提供跟用户相关的实例。基于我们的例子,它可以提供用户用例。
307 |
308 | ```java
309 | @Module
310 | public class UserModule {
311 | @Provides @PerActivity GetUserListUseCase provideGetUserListUseCase(GetUserListUseCaseImpl getUserListUseCase) {
312 | return getUserListUseCase;
313 | }
314 |
315 | @Provides @PerActivity GetUserDetailsUseCase provideGetUserDetailsUseCase(GetUserDetailsUseCaseImpl getUserDetailsUseCase) {
316 | return getUserDetailsUseCase;
317 | }
318 | }
319 | ```
320 | #### 整合在一起
321 |
322 | 现在我们已经实现了依赖注入图,但是我该如何注入?我们需要知道,Dagger给了我们一堆选择用来注入依赖:
323 |
324 | 1. 构造方法注入:在类的构造方法前面注释`@Inject`
325 | 2. 成员变量注入:在类的成员变量(非私有)前面注释`@Inject`
326 | 3. 函数方法注入:在函数前面注释`@Inject`
327 |
328 | 这个顺序是`Dagger`建议使用的,因为在运行的过程中,总会有一些奇怪的问题甚至是空指针,这也意味着你的依赖在对象创建的时候可能还没有初始化 完成。这在`Android`的`activity`或者`fragment`中使用成员变量注入会经常遇到,因为我们没有在它们的构造方法中使用。
329 |
330 | 回到我们的例子中,看一下我们是如何在`BaseActivity`中注入一个成员变量。在这个例子中,我们注入了一个叫`Navigator`的类,它是我们应用中负责管理导航的类。
331 |
332 | ```java
333 | public abstract class BaseActivity extends Activity {
334 |
335 | @Inject Navigator navigator;
336 |
337 | @Override
338 | protected void onCreate(Bundle savedInstanceState) {
339 | super.onCreate(savedInstanceState);
340 | this.getApplicationComponent().inject(this);
341 | }
342 |
343 | protected ApplicationComponent getApplicationComponent() {
344 | return ((AndroidApplication)getApplication()).getApplicationComponent();
345 | }
346 |
347 | protected ActivityModule getActivityModule() {
348 | return new ActivityModule(this);
349 | }
350 | }
351 | ```
352 |
353 | `Navigator`类是成员变量注入的,由`ApplicationModule`里面`@Provide`注解显示提供的。最终我们初始化 `component`然后调用`inject()`方法注入成员变量。我们通过在`Activity`的`onCreate()`方法中调用 `getApplicationComponent()`,完成这些操作。
354 | `getApplicationComponent()`方法放在这儿是为了复用性,它 的主要作用是为了获取实例化的`ApplicationComponent`对象。
355 |
356 | 在`Fragment`的`presenter`中我们也做了同样的事情,这儿的获取方法有一点不一样,因为我们使用的是`per-activity`范围限定的`component`。所以我们注入到`UserDetailsFragment`中的`UserComponent`其实是驻留在 `UserDetailsActivity`中的。
357 |
358 | ```java
359 | private UserComponent userComponent;
360 | ```
361 | 我们必须在`activity`的`onCreate()`方法中用下面的方式初始化。
362 |
363 | ```java
364 | private void initializeInjector() {
365 | this.userComponent = DaggerUserComponent.builder()
366 | .applicationComponent(getApplicationComponent())
367 | .activityModule(getActivityModule())
368 | .build();
369 | }
370 | ```
371 | `Dagger`会处理我们的注解,为`components`生成实现并重命名加上`“Dagger”`前缀。因为这个是一个组合的`component`,所以在构建的时候,我们必须把所有的依赖传进去(`components`和`modules`)。现在我们的`component`已经准备好了,接着为了可以满足`fragment`的依赖需求,我们写一个获取方法:
372 |
373 | ```java
374 | @Override public UserComponent getComponent() {
375 | return userComponent;
376 | }
377 | ```
378 | 我们现在可以利用`get`方法获取创建的`component`,然后调用`inject()`方法将`Fragment`作为参数传进去,这样就完成了绑定`UserDetailsFragment`依赖。
379 |
380 | ```java
381 | @Override public void onActivityCreated(Bundle savedInstanceState) {
382 | super.onActivityCreated(savedInstanceState);
383 | this.getComponent.inject(this);
384 | }
385 | ```
386 | 这里面有一些地方重构了的,我可以告诉你一个重要的思想(来自官方的例子)是:
387 |
388 | ```java
389 | public interface HasComponent {
390 | C getComponent();
391 | }
392 | ```
393 | 因此,客户端(例如`fragment`)可以获取并且使用`component`(来自`activity`):
394 |
395 | ```java
396 | @SuppressWarnings("unchecked")
397 | protected C getComponent(Class componentType) {
398 | return componentType.cast(((HasComponent)getActivity()).getComponent());
399 | }
400 | ```
401 | 这儿使用了强制转换,不论这个客户端不能获取到能用的`component`,但是至少很快就会失败。
402 |
403 | ---
404 |
405 | ### Dagger2生成的代码
406 |
407 | 在了解`Dagger`的主要特征之后,我们再来看看内部构造。为了举例说明,我们还是用`Navigator`类,看看它是如何创建和注入的。首先我们看一下我们的`DaggerApplicationComponent`。
408 |
409 | ```java
410 | @Generated("dagger.internal.codegen.ComponentProcessor")
411 | public final class DaggerApplicationComponent implements ApplicationComponent {
412 | private Provider provideNavigatorProvider;
413 | private MembersInjector baseActivityMembersInjector;
414 |
415 | private DaggerApplicationComponent(Builder builder) {
416 | assert builder != null;
417 | initialize(builder);
418 | }
419 |
420 | public static Builder builder() {
421 | return new Builder();
422 | }
423 |
424 | private void initialize(final Builder builder) {
425 | this.provideNavigatorProvider = ScopedProvider.create(ApplicationModule_ProvideNavigatorFactory.create(builder.applicationModule));
426 | this.baseActivityMembersInjector = BaseActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideNavigatorProvider);
427 | }
428 |
429 | @Override
430 | public void inject(BaseActivity baseActivity) {
431 | baseActivityMembersInjector.injectMembers(baseActivity);
432 | }
433 |
434 | public static final class Builder {
435 | private ApplicationModule applicationModule;
436 |
437 | private Builder() {
438 | }
439 |
440 | public ApplicationComponent build() {
441 | if (applicationModule == null) {
442 | throw new IllegalStateException("applicationModule must be set");
443 | }
444 | return new DaggerApplicationComponent(this);
445 | }
446 |
447 | public Builder applicationModule(ApplicationModule applicationModule) {
448 | if (applicationModule == null) {
449 | throw new NullPointerException("applicationModule");
450 | }
451 | this.applicationModule = applicationModule;
452 | return this;
453 | }
454 | }
455 | }
456 | ```
457 | 有两个重点需要注意:
458 | - 第一个:由于我们要将依赖注入到`activity`中,所以会得到一个注入这个比成员的注入器(由`Dagger`生成的`BaseActivity_MembersInjector`):
459 |
460 | ```java
461 | @Generated("dagger.internal.codegen.ComponentProcessor")
462 | public final class BaseActivity_MembersInjector implements MembersInjector {
463 | private final MembersInjector supertypeInjector;
464 | private final Provider navigatorProvider;
465 |
466 | public BaseActivity_MembersInjector(MembersInjector supertypeInjector, Provider navigatorProvider) {
467 | assert supertypeInjector != null;
468 | this.supertypeInjector = supertypeInjector;
469 | assert navigatorProvider != null;
470 | this.navigatorProvider = navigatorProvider;
471 | }
472 |
473 | @Override
474 | public void injectMembers(BaseActivity instance) {
475 | if (instance == null) {
476 | throw new NullPointerException("Cannot inject members into a null reference");
477 | }
478 | supertypeInjector.injectMembers(instance);
479 | instance.navigator = navigatorProvider.get();
480 | }
481 |
482 | public static MembersInjector create(MembersInjector supertypeInjector, Provider navigatorProvider) {
483 | return new BaseActivity_MembersInjector(supertypeInjector, navigatorProvider);
484 | }
485 | }
486 | ```
487 | 这个注入器一般都会为所有`activity`的注入成员提供依赖,只要我们一调用`inject()`方法,就可以获取需要的字段和依赖。
488 |
489 | - 第二个重点:关于我们的`DaggerApplicationComponent`类,我们有一个`Provider`,它不仅仅是一个提供实例的接口,它还是被`ScopedProvider`构造出来的,可以记录创建实例的范围。
490 |
491 | `Dagger`还会为我们的`Navigator`类生成一个名叫`ApplicationModule_ProvideNavigatorFactory`的工厂,这个工厂可以传递上面提到的范围参数然后得到这个范围内的类的实例。
492 |
493 | ```java
494 | @Generated("dagger.internal.codegen.ComponentProcessor")
495 | public final class ApplicationModule_ProvideNavigatorFactory implements Factory {
496 | private final ApplicationModule module;
497 |
498 | public ApplicationModule_ProvideNavigatorFactory(ApplicationModule module) {
499 | assert module != null;
500 | this.module = module;
501 | }
502 |
503 | @Override
504 | public Navigator get() {
505 | Navigator provided = module.provideNavigator();
506 | if (provided == null) {
507 | throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
508 | }
509 | return provided;
510 | }
511 |
512 | public static Factory create(ApplicationModule module) {
513 | return new ApplicationModule_ProvideNavigatorFactory(module);
514 | }
515 | }
516 | ```
517 | 这个类非常简单,它代表我们的`ApplicationModule`(包含`@Provide`方法)创建了`Navigator`类。
518 |
519 | 总之,上面的代码看起来就像是手敲出来的,而且非常好理解,便于调试。其余还有很多可以去探索,你们可以通过调试去看看Dagger如何完成依赖绑定的。
520 |
521 | ---
522 | - 邮箱 :[elisabethzhen@163.com](elisabethzhen@163.com)
523 | - Good Luck!
--------------------------------------------------------------------------------