├── 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 | ![NoteBook](http://www.polyvore.com/cgi/img-thing?.out=jpg&size=l&tid=82336839) 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 | ![Activity的生命周期](http://img.my.csdn.net/uploads/201007/28/0_12803210018q71.gif) 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 | ![异常情况下Activity的重建过程](http://7xs99u.com1.z0.glb.clouddn.com/image/jpg/%E5%BC%82%E5%B8%B8%E6%83%85%E5%86%B5%E4%B8%8BActivity%E7%9A%84%E9%87%8D%E5%BB%BA%E8%BF%87%E7%A8%8B.png) 50 | 51 | 而在视图方面,当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据。 52 | 53 | 其实每个View都有`onSaveInstanceState`和`onRestoreInstanceState`,关于保存和恢复View层级结构,系统的工作流程如下: 54 | 55 | ![关于保存与恢复View层级结构](http://7xs99u.com1.z0.glb.clouddn.com/image/jpg/%E5%85%B3%E4%BA%8E%E4%BF%9D%E5%AD%98%E5%92%8C%E6%81%A2%E5%A4%8DView%E5%B1%82%E7%BA%A7%E7%BB%93%E6%9E%84.png) 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 | ![singleTask 的工作流程](http://7xs99u.com1.z0.glb.clouddn.com/image/png/singleTask%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B.png) 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 | ![TaskAddinity](http://7xs99u.com1.z0.glb.clouddn.com/image/png/TaskAffinity%E5%B1%9E%E6%80%A7.png) 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 |