-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
269 lines (269 loc) · 138 KB
/
search.xml
File metadata and controls
269 lines (269 loc) · 138 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[IntelliJ IDEA Mac快捷键]]></title>
<url>%2F2018%2F03%2F25%2Fidea-keymap-on-mac%2F</url>
<content type="text"><![CDATA[智能提示 ⌘ -> command⇧ -> shift⌥ -> option⬆ -> 上箭头⬇ -> 下箭头⌃ -> Control 编辑 快捷键 说明 ⌘ + F 在当前窗口查找 ⌘ + ⇧ + F 在全工程查找 ⌘ + ⇧ + ⌥ + N 查找类中的方法或变量 F3 / ⇧ + F3 移动到搜索结果的下/上一匹配处 ⌘ + R 在当前窗口替换 ⌘ + ⇧ + R 在全工程替换 ⌘ + ⇧ + V 可以将最近使用的剪贴板内容选择插入到文本 ⌥ + ⇧ + Up/Down 向上/下移一行 ⌘ + ⇧ + Up/Down 向上/下移动语句 ⌘ + / 注释 - // ⌘ + ⇧ + / 注释 - /**/ ⇧ + Enter 向下插入新行 ⌘ + Enter 上插一行 ⌘ + ⇧ + F7 高亮显示所有该文本,按 Esc 高亮消失 ⌘ + W 可以选择单词继而语句继而行继而函数 ⌘ + ⇧ + W 取消选择光标所在词 ⌥ + Left/Right 移动光标到前/后单词 ⌥ + Backspace 按单词删除 ⌘ + [/] 移动到前/后代码块 ⌘ + ⇧ + Left/Right/[/] 选中跳跃范围内的代码 ⌘ + Y/X 删除行 ⌘ + D 复制行 ⌘ + ⇧ + U 大小写转化 ⌘ + ⌥ + V 可以引入变量。例如:new String(); 自动导入变量定义 ⌘ + ⌥ + T 可以把代码包在一个块内,例如:try/catch ⌘ + ⌥ + L 格式化代码 ⌘ + ⌥ + I 将选中的代码进行自动缩进编排,这个功能在编辑 JSP 文件时也可以工作 ⌘ + ⌥ + O 优化导入的类和包 ⌘ + +/- 当前方法展开、折叠 ⌘ + ⇧ + +/- 全部展开、折叠 重构 快捷键 说明 ⌘ + ⇧ + ⌥ + T 重构功能大汇总快捷键 ⇧ + F6 重命名 ⌘ + ⌥ + V 提取变量 ⌘ + O 重写父类方法 代码生成 快捷键 说明 fori 生成循环 sout System.out.println(); ⌘ + J 可以查看所有代码模板 ⌘ + ⌥ + J 用动态模板环绕 ⌘ + ⇧ + Enter 自动补全末尾的字符(括号,分号),例如敲完if/for时也可以自动补上{}花括号。 ⌥ + Enter 导入包,快速修复 后缀自动补全功能(Postfix Completion) 要输入 for(User user : users) 只需输入 user.for + Tab。 要输入 Date birthday = user.getBirthday(); 只需输入 user.getBirthday().var + Tab 即可。 | 文件 快捷键 说明 ⌘ + Delete 删除文件 ⌃ + ⌥ + N 新建一切文件 F5 复制类 F6 移动类 工具栏 快捷键 说明 ⌃ + H 打开类层次窗口,查看类的继承关系 ⌘ + 1 快速打开或隐藏工程面板 ⇧ + Esc 不仅可以把焦点移到编辑器上,而且还可以隐藏当前(或最后活动的)工具窗口 查找定位 快捷键 说明 ⌘ + F7 可以查询当前元素在当前文件中的引用,然后按 F3 可以选择 ⌥ + F7 查找整个工程中使用地某一个类、方法或者变量的位置 ⇧ + ⇧ Search Everywhere 功能,可在一个弹出框中搜索任何东西,包括类、资源、配置项、方法等等 ⌘ + N 快速打开某个类 ⌘ + ⇧ + N 快速打开文件或资源 ⌘ + B / ⌘ + Click 快速打开光标处的类或方法(跳转到定义处) ⌘ + ⌥ + B 跳转到方法实现处 F2 / ⇧ + F2 移动到有错误的代码 ⌘ + U 转到父类 ⌘ + G 定位行 ⌘ + ⌥ + left/right 返回至上次浏览的位置 ⌘ + E 最近的文件 ⌘ + ⇧ + E 最近更改的文件 ⌥ + ⇧ + C 最近的更改 ⌥ + F1 查找代码所在位置 ⌘ + ⌥ + F7 显示用法 ⌘ + I 实现方法 ⌘ + ⌥ + N 内联 方法相关 快捷键 说明 ⌘ + P 可以显示参数信息 ⌘ + F12 查看当前文件的结构 调试部分、编译 快捷键 说明 ⌘ + F2 停止 ⌥ + ⇧ + F9 选择 Debug ⌥ + ⇧ + F10 选择 Run ⌘ + ⇧ + F9 编译 ⌘ + ⇧ + F10 运行 ⌘ + ⇧ + F8 查看断点 F7 步入 Step into F8 步过 Step over F9 恢复程序 Continue ⇧ + F7 智能步入 ⇧ + F8 步出 ⌥ + ⇧ + F8 强制步过 ⌥ + ⇧ + F7 强制步入 ⌥ + F9 运行至光标处 ⌥ + F10 定位到断点 ⌘ + ⌥ + F9 强制运行至光标处 ⌘ + F8 切换行断点 ⌘ + F9 生成项目 ⌘ + ⇧ + C 复制路径 ⌘ + ⌥ + ⇧ + C 复制引用,必须选择类名 ⌘ + ⌥ + Y 同步 ⌘ + ~ 快速切换方案(界面外观、代码风格、快捷键映射等菜单) ⇧ + F12 还原默认布局 ⌘ + ⇧ + F12 隐藏/恢复所有窗口 ⌘ + F4 关闭 ⌘ + ⇧ + F4 关闭活动选项卡 ⌘ + Tab 转到下一个拆分器 ⌘ + ⇧ + Tab 转到上一个拆分器 切换窗口 快捷键 说明 ⌘ + 1 项目结构 ⌘ + 2 收藏 ⌘ + 3 搜索结果 ⌘ + 4 运行 ⌘ + 5 调试 ⌘ + 6 TODO ⌘ + 7 结构 ⌃ + Tab 切换 tab 其他 快捷键 说明 ⌘ + ⇧ + A 可以查找所有命令,并且每个命令后面还有其快捷键 在任意菜单或显示窗口,都可以直接输入你要找的单词,idea 就会自动为你过滤。 测试 快捷键 说明 ⌘ + ⌥ + T 创建单元测试用例]]></content>
<tags>
<tag>Mac</tag>
</tags>
</entry>
<entry>
<title><![CDATA[许久未见,你,还好吗?]]></title>
<url>%2F2018%2F03%2F24%2F%E8%AE%B8%E4%B9%85%E6%9C%AA%E8%A7%81%EF%BC%8C%E4%BD%A0%EF%BC%8C%E8%BF%98%E5%A5%BD%E5%90%97%EF%BC%9F%2F</url>
<content type="text"><![CDATA[好久不见,你,还好吗?]]></content>
<categories>
<category>life</category>
</categories>
<tags>
<tag>life</tag>
</tags>
</entry>
<entry>
<title><![CDATA[从源码角度分析 Handler Looper MessageQueue]]></title>
<url>%2F2016%2F08%2F16%2Fsource-code-of-handler-and-looper%2F</url>
<content type="text"><![CDATA[前言首先我们要知道,创建Handler之前,要先创建与之配套的Looper。在主线程中,系统已经初始化了一个 Looper 对象,因此程序直接创建 Handler 对象即可,然后就可以通过 Handler 来发送、处理消息了。在子线程中,必须自己创建一个 Looper 对象,并启动它。创建 Looper 对象调用它的 prepare 方法即可(prepare 方法保证每个线程最多只有一个 Looper 对象),然后调用 Looper 的静态 loop 方法来启动它。loop 方法使用一个死循环不断地从MessageQueue 中取消息,并将取出的消息分发给该消息对应的 Handler 处理。至于它们具体做了哪些事,我们会在后面详细讲述。 Looper的创建我们先看一下Looper的构造函数 1234private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();} 首先我们注意到该构造方法是被private修饰的,也就是说我们无法通过new的方式来创建Looper。其次,我们可以从Looper的构造方法中看出,在创建Looper的时候,创建了与之配套的MessageQueue,然后获取了创建当前Looper线程的引用。而要想创建Looper,只需调用Looper.prepare();。该方法保证了一个线程只能创建一个与之相关联的Looper,并且将创建出的Looper与当前线程绑定起来。 12345678910public static void prepare() { prepare(true);}private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));} Handler的创建Looper对象成功创建之后,我们再来看看Handler。当我们调用Handler的无参构造函数创建Handler时,它内部调用了另一个重载的构造方法this(null, false)。 123456789101112public Handler(Callback callback, boolean async) { ...//something we just ignore mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async;} 从构造方法中,我们可以看出,handler获取了与当前线程相关联的Looper及MessageQueue的引用。 Handler 发送消息创建完Handler之后,我们先从handler.sendMessage()说起: 1234567891011121314151617181920212223242526public class MainActivity extends Activity{ protected void onCreate(Bundle onSavedInstanceState){ super.onCreate(onSavedInstanceState); setContentView(R.layout.main); MyRunnable mRunnable = new MyRunnable(); Thread mThread = new Thread(mRunnable); mThread.start(); } public static Handler = new Handler(){ @Override public void handleMessage(Message msg){ //do some magic } }; static class MyRunnable implements Runnable{ @Override public void run(){ Message msg = Message.obtain(); //do some magic handler.sendMessage(msg); } }} 而不管我们调用sendEmptyMessage()或者是sendMessage(),最终都会走到这里: 12345678910public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis);} 关键是最后return时,调用了enqueueMessage(),我们一起看一下该方法的具体实现: 1234567private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis);} 从enqueueMessage的方法实现可以看出,要发送的消息获取到了当前Handler的引用,也就是msg.target 。然后这条信息被加入到了与当前线程相关联的MessageQueue中。到此,发送消息的逻辑已经结束。那么Handler处理消息的方法又是在什么时候回调的呢?要弄明白这一点,我们要对Looper.loop()进行分析。 Looper.loop();前面我们也已经提过,要创建一个Handler其实是需要三个步骤的: 调用Looper.prepare(); 创建Handler 调用Looper.loop();前面两步我们已经讲解过了,那你肯定会好奇,loop()中到底做了什么呢?我们一起来看一下。 12345678910111213141516171819202122232425262728293031public static void loop() { //获取与当前线程相关联的Looper对象 final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //获取与Looper对象相匹配的MessageQueue final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ...//something we just ignore msg.target.dispatchMessage(msg); ...//something we just ignore // Mark the message as in use while it remains in the recycled object pool. Clear out all other details. msg.recycleUnchecked(); }} 从源码可以看出,loop()其实调用了一个死循环,不断的从与Looper配套的MessageQueue中取消息,然后调用msg.target.dispatchMessage(msg);进行消息的分发。前面Handler发送消息的时候,我们已经分析过,每个要发送的Message都获取到了发送它的Handler的引用,也就是msg.target。因此这里msg.target.dispatchMessage(msg);其实也就是调用了handler的dispatchMessage进行消息的分发。 Handler的handleMessage()何时被回调?我们一起来看一下handler的dispatchMessage(): 123456789101112public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }} 很明显,就是在这里,Handler的handleMessage被回调了。综上,我们可以说,是在Looper.loop()中,当循环不断的从MessageQueue中获取消息时,间接调用了Handler的handleMessage()方法。]]></content>
<categories>
<category>android</category>
<category>tech</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android Studio 之 JNI 开发详解]]></title>
<url>%2F2016%2F08%2F02%2FJNI-Develop-in-Android-Studio%2F</url>
<content type="text"><![CDATA[前言 什么是NDK?NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。 为什么使用NDK?1.)代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。2.)可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。3.)提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。4.)便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。 什么是JNI?JNI全称为:Java Native Interface。JNI 是本地编程接口,它使得在 Java 虚拟机内部运行的 Java 代码能够与用其它语言(如 C、C++)编写的代码进行交互。 为什么使用JNI?JNI的目的是使java方法能够调用c实现的一些函数。 安卓中的so文件是什么?Android中用到的so文件是一个c++的函数库。在android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供java调用。Android中用到的so文件是一个c++的函数库。在android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供java调用。Android中用到的so文件是一个c++的函数库。在android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供java调用。Android中用到的so文件是一个c++的函数库。在android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供java调用。 本例开发环境如下:操作系统:Mac开发环境:Android Studio 2.2 Beta3 + NDK r12 + Gradle 2.14.1 NDK安装 从Android Studio安装(需翻墙)1.)打开AndroidStudio,选择顶部工具条,Tools->Android->SDK Manager2.)在弹出来的对话框中选择SDK Tools选项卡3.)勾选上图中NDK,点击 Apply,开始安装4.)安装完成后,重启Android Studio 从AndroidDevTools安装1.)打开AndroidDevTools网页,选择导航栏中Android SDK Tools->NDK,选择相应平台的NDK开始下载。2.)下载完成后,将NDK解压到某个文件夹下,打开Android Studio,选择File->Project Structure在弹出来的对话框中,配置NDK路径,如下所示: JNI开发下面我们就一步一步来完成一个示例,从C语言编写的程序中获取字符串,然后在TextView上显示出来。 新建一个Android Project,命名为 MyApplication注意:项目路径中不能有空格! 项目新建完成后,默认为Android视图,这里为了更清楚的展示,我们切换到Project视图。项目结构如下: 在项目gradle.properties文件中加上以下代码,表示我们要使用NDK进行开发。 1android.useDeprecatedNdk=true 在项目local.properties中加入ndk和sdk的路径: 12sdk.dir=/Users/用户名/android-sdk-macosxndk.dir=/Users/用户名/android-sdk-macosx/ndk-bundle 在app文件夹下的build.gradle中的defaultConfig里加入如下代码 1234ndk{ moduleName "hello" //生成的so文件名字,调用C程序的代码中会用到该名字 abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种平台下的so库} 如下所示: 打开布局文件activity_main.xml,我们来添加一个TextView显示从C程序中返回的字符串 123456789101112131415161718<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="me.jockio.myapplication.MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" /></RelativeLayout> 打开MainActivity.java,添加如下代码: 12345678910111213141516171819public class MainActivity extends AppCompatActivity { //固定写法,表示我们要加载的资源文件为libhello.so static { System.loadLibrary("hello"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = (TextView) findViewById(R.id.textView); textView.setText(getStringFromNative()); } //声明一个本地方法,用native关键字修饰 public native String getStringFromNative();} 生成.h头文件打开Android Studio底部的Terminal,默认命令行窗口路径已经在当前项目,输入以下命令: 12cd app/src/main/javajavah -jni 包名+类名 执行完上面两条命令后,会自动生成.h文件生成.h文件内容如下:这里关键部分就是:1JNIEXPORT jstring JNICALL Java_me_jockio_myapplication_MainActivity_getStringFromNative (JNIEnv *, jobject); 新建jni文件夹,并拷贝上面生成的.h文件到jni目录选择File->New->Folder->JNI Folder在弹出的对话框中勾选Change Folder Location,并在下面输入文件夹名,如下图所示: 在jni目录下,右键新建C文件,文件名任意,输入如下内容: 1234567//引入上面生成的头文件,并实现头文件中声明的方法#include "me_jockio_myapplication_MainActivity.h"JNIEXPORT jstring JNICALL Java_me_jockio_myapplication_MainActivity_getStringFromNative (JNIEnv *env, jobject obj){ char *str="String from native C"; return (*env)->NewStringUTF(env, str);} 注意观察函数方法名为:Java_包名_类名_方法名,了解到这些后我们以后就可以不生成.h文件,而是直接去写.c文件了。 选择 Build->Make Project,看app/build/intermediates/ndk/debug/lib目录下是否生成.so文件,如果没有生成,选择 Build->Clean Project,等clean完成后,再Build->Rebuild Project,一般经过上面两步以后都能够解决问题。 打开模拟器,运行Android程序。这里可以看到已经从libhello.so文件中读取到字符串,并显示在了TextView中。 参考文章 在Android Studio上的NDK开发 NDK-JNI实战教程 Android Studio第一个jni程序]]></content>
<categories>
<category>android</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[屏幕适配之百分比布局]]></title>
<url>%2F2016%2F07%2F22%2Fpercent-layout-on-android%2F</url>
<content type="text"><![CDATA[前言刚刚复习多屏幕适配的时候,想到之所以要对手机屏幕进行适配,是因为Android手机的屏幕碎片化太过于严重。但转念一想,电脑屏幕的碎片化相对于手机只会有过之无不及吧,网上查找了下,发现前端之所以没有强调屏幕适配,是因为它们引入了百分比。 然后谷歌搜了下,才发现去年(2015年)谷歌就已经在Android中引入了百分比来适配不同的屏幕,原来我已经out了…… 百分比布局介绍谷歌目前只提供了 PercentRelativeLayout 以及 PercentFrameLayout 的两种布局,它们支持的属性有: layout_widthPercent、layout_heightPercent layout_marginPercent、layout_marginLeftPercent layout_marginTopPercent、layout_marginRightPercent layout_marginBottomPercent、layout_marginStartPercent layout_marginEndPercent 在此基础上鸿洋大神提供了 PercentLinearLayout (见博客),并已经引入到Github中。 使用步骤1.在gradle文件中添加依赖 123dependencies { compile 'com.android.support:percent:22.2.0'} 2.鸿洋大神实现的PercentLinearLayout 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081import android.content.Context;import android.content.res.TypedArray;import android.support.percent.PercentLayoutHelper;import android.util.AttributeSet;import android.view.ViewGroup;import android.widget.LinearLayout;public class PercentLinearLayout extends LinearLayout{ private PercentLayoutHelper mPercentLayoutHelper; public PercentLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); mPercentLayoutHelper = new PercentLayoutHelper(this); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mPercentLayoutHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mPercentLayoutHelper.handleMeasuredStateTooSmall()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mPercentLayoutHelper.restoreOriginalParams(); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } public static class LayoutParams extends LinearLayout.LayoutParams implements PercentLayoutHelper.PercentLayoutParams { private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs); } @Override public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() { return mPercentLayoutInfo; } @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public LayoutParams(MarginLayoutParams source) { super(source); } }} 百分比线性布局12345678910111213141516171819202122232425262728<?xml version="1.0" encoding="utf-8"?><com.myapplication.view.PercentLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="0dp" android:layout_height="0dp" app:layout_widthPercent="50%" app:layout_heightPercent="10%" android:background="#d1f756" android:gravity="center" android:text="width:50% height:10%"/> <TextView android:layout_width="0dp" android:layout_height="0dp" app:layout_widthPercent="80%" app:layout_heightPercent="10%" app:layout_marginTopPercent="1%" android:background="#56f7b1" android:gravity="center" android:text="width:80% height:10%"/> </com.myapplication.view.PercentLinearLayout> 百分比相对布局12345678910111213141516171819202122232425262728293031323334<android.support.percent.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:id="@+id/top_left" android:layout_width="0dp" android:layout_height="0dp" android:layout_alignParentTop="true" android:background="#ff44aacc" app:layout_heightPercent="20%" app:layout_widthPercent="70%" /> <View android:id="@+id/top_right" android:layout_width="0dp" android:layout_height="0dp" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/top_left" android:background="#ffe40000" app:layout_heightPercent="20%" app:layout_widthPercent="30%" /> <View android:id="@+id/bottom" android:layout_width="match_parent" android:layout_height="0dp" android:layout_below="@+id/top_left" android:background="#ff00ff22" app:layout_heightPercent="80%" /></android.support.percent.PercentRelativeLayout> 百分比帧布局1234567<android.support.percent.PercentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- ... XML CODE --></android.support.percent.PercentFrameLayout>]]></content>
<categories>
<category>android</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一种无需为ListView设置ViewHolder的写法]]></title>
<url>%2F2016%2F07%2F21%2Fan-elegent-method-of-optmize-listview%2F</url>
<content type="text"><![CDATA[【译】原文: https://www.bignerdranch.com/blog/customizing-android-listview-rows-subclassing/ 每个Android开发者都会碰到自定义ListView中item布局,并填充数据的情况,而你首先想到的应该就是ViewHolder模式吧。但是ViewHolder模式使用起来太过于死板,实际上我们可以做得更好。在本文中,我们会探索另外一种可供选择的方式:使用RelativeLayout的子类来封装完成定制化的工作。 目标为了说明目的,我们首先创建了一个典型自定义的ListView,它的每一行包括一个ImageView和两个排列在它旁边的TextView,每个item的父布局为RelativeLayout,如下图所示。你可以在Github上查看它的代码。 当为ListView中的item创建自定义view的时候,我们有下面的需求: 使用自定义布局管理子控件的排列方式 当滚动时,重复利用已创建的view 高效识别子视图,并为其填充数据 ViewHolder模式所带来的问题我们在网上看到的一种实现都大同小异,这就是ViewHolder模式。简而言之,它需要下面的步骤: 为ListView中item创建一个布局 创建ViewHolder类,存储item中子控件 对于ListView中的每个item,创建一个ViewHolder的实例,并通过findViewById()与ViewHolder中成员绑定 使用setTag(),将item与ViewHolder绑定 对重复使用的item重用ViewHolder对象 因为下面的原因,我并不喜欢这个模式: 它把太多的职责放在了Adapter的getView()方法中 ViewHolder类过于公式化 每个视图需要根据tag强转为合适的ViewHolder类型 ViewHolder类需要知道list中item内部细节,违反了封装的特点 因此,我决定提供一种可供选择的方式——子类化,而不是一直抱怨。 使用子类实现自定义我会使用RelativeLayout的子类命名为ItemView作为我们是定义布局的根视图,而不是创建一个一般的视图——ViewHolder类与Adapter类的实现,它们了解太多内部实现的细节。Item是呈现数据的模型对象,它里面有三个属性:图片的url,标题以及描述。 1234567public class Item { private String mImageUrl; private String mTitle; private String mDescription; // constructor, getters and setters elided} ItemView有责任将Item中数据展示在屏幕上。作为ItemView的使用者(在Adapter的子类中),我想我的职责越简单越好,我真的只需要做好下面两件事: 为ListView的每一个item创建或者重用ItemView 将当前行的数据Item与ItemView关联起来 我们可以在ItemAdapter类中查看相关的API: 123456789101112131415public class ItemAdapter extends ArrayAdapter<Item> { public ItemAdapter(Context c, List<Item> items) { super(c, 0, items); } @Override public View getView(int position, View convertView, ViewGroup parent){ ItemView itemView = (ItemView)convertView; if (null == itemView) itemView = ItemView.inflate(parent); itemView.setItem(getItem(position)); return itemView; }} 这里有两行代码很有意思,首先,如果我们没有可重用的view,那么就调用ItemView.inflate(ViewGroup)这个静态方法获取ItemView的实例。其次,我们使用setItem(Item)方法为当前ItemView提供要展示的数据。所有这些细节都封装在ItemVIew内部。 ItemView通过成员变量存储对子视图的引用,把自己作为自己的ViewHolder。 1234567public class ItemView extends RelativeLayout { private TextView mTitleTextView; private TextView mDescriptionTextView; private ImageView mImageView; ...} inflate(ViewGroup)这个静态方法使创建并正确配置ItemView变得非常简单,同时使用XML文件完成自定义布局的配置。 12345public static ItemView inflate(ViewGroup parent) { ItemView itemView = (ItemView)LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_view, parent, false); return itemView;} 它使用参数中的parent来获取context,然后将布局文件映射为视图并返回。布局文件如下所示: 12345<com.bignerdranch.android.listitemviewdemo.ItemView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="5dp" /> 该布局文件只包含一个ItemView,在ItemView中声明了边距。要特别注意的是,它并没有子节点,那么我们需要的ImageView以及两个TexeView在哪呢?实际上,在ItemView的构造方法中,我们完成了对子视图的创建。 12345public ItemView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); LayoutInflater.from(context).inflate(R.layout.item_view_children, this, true); setupChildren();} 该构造方法最终会在inflate包含ItemView视图的时候调用,它首先会调用父类的构造方法,然后infalte ItemView中的子视图。子视图的布局文件如下: 123456789101112131415161718192021222324252627282930<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" ><ImageView android:id="@+id/item_imageView" android:background="@android:color/darker_gray" android:layout_width="60dp" android:layout_height="60dp" android:layout_margin="5dp" android:contentDescription="@string/item_imageView_contentDescription"/><TextView android:id="@+id/item_titleTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/item_imageView" android:text="title text"/><TextView android:id="@+id/item_descriptionTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/item_imageView" android:layout_below="@id/item_titleTextView" android:layout_marginTop="5dp" android:text="description text"/></merge> 根节点为merge标签,它表明在inflation的过程中,merge标签下的所有子控件都会被添加到parent参数中,并传递到构造方法中的inflate(...)方法。之后我们调用setupChildren()方法,通过findViewById(int)完成查找控件的工作,这样就可以把控件与成员变量关联起来了。 12345private void setupChildren() { mTitleTextView = (TextView) findViewById(R.id.item_titleTextView); mDescriptionTextView = (TextView) findViewById(R.id.item_descriptionTextView); mImageView = (ImageView) findViewById(R.id.item_imageView);} 到此,ItemView就已经实现了传统的VIewHolder的功能,能通过自己的成员变量将子视图的引用缓存起来。 为了让大家信服,我同时提供了setItem(Item)方法让调用者把Item与每个子视图绑定起来。 12345public void setItem(Item item) { mTitleTextView.setText(item.getTitle()); mDescriptionTextView.setText(item.getDescription()); // TODO: set up image URL} 这就是我们实现了一种新的模式。尽管我们需要创建多创建一个布局文件,并且为了inflation多实现了一个方法,但是优点是显而易见的: Adapter类的实现被大大的简化了 能够很容易的通过xml文件或者代码创建ItemView 任何扩展都能够在ItemView以及布局文件内部来完成 不再需要创建多余的ViewHolder类 Subclassing works下一次想到ViewHolder模式的时候,尝试用一下本文提到的新的模式。该模式良好的封装很容易让你充分自定义ItemView而不再需要考虑那些令人烦扰的细节。在视图重用的过程中,我敢说它跟ViewHolder模式一样好。]]></content>
<categories>
<category>android</category>
<category>译</category>
</categories>
<tags>
<tag>android</tag>
<tag>译</tag>
</tags>
</entry>
<entry>
<title><![CDATA[the Summary of June in 2016]]></title>
<url>%2F2016%2F07%2F13%2Fthe-summary-of-June-in-2016%2F</url>
<content type="text"><![CDATA[实习已经一个月了,终于下定决定来做个总结,哈哈,自己也觉得太不容易了……虽然也已经过去了一个月,上班也没有太多的事情,都是偶尔改一个bug,大部分时间都在自己学自己的了。下面就对这一个月学习到的东西做个总结吧。 接触项目第一次接触工作中的项目,而且是一个比较大的项目,从SVN上checkout的时候,打开一看,整个人都不好了。项目文件分了几十个包,每个包里又有上百个类,而大部分类的代码都在2000行以上……可能这是多人协作的常态了,也只能慢慢适应了。 项目背景由于公司是外企,项目组做的APP又是面向日本用户的,还是与日方协作开发的,因此不管是APP、文档,还是代码中的注释,文字通通都是日文,这无疑给理解项目逻辑带来了严重的挑战。尽管国内有百度翻译,但是真的用在专业术语的翻译上,那都是个屁! 可能你会说,不是有那么多界面的么,根据布局文件查找相应的Activity不就好了?其实开始我也是这样想的,但真的打开项目的时候,整个人真的都是懵逼的——布局文件只有寥寥几个,而Activity却有上百个,项目中布局复用真的是太严重了,当然了,这对项目来说是好事。 项目上手好在项目的debug信息比较详细,每一个类的方法在开始调用的时候都会打印出该方法所属的类的类名与该方法的方法名,在方法即将结束的时候也会打印该方法所属的类的类名与该方法的方法名。凭借这些dubug信息,开始慢慢“玩”APP,每跳转一个界面看看都用到了哪些类,该类所属的Activity又是哪一个。点击界面上的按钮会触发哪些方法,就这样过了两周,开始慢慢测试一些小bug,对项目逻辑也有了些了解。 然后又接触了项目文档,虽然文档给的有些晚……文档是画面的迁移图,从某个界面经过相应的操作会跳转到哪一个界面,都描述的非常清楚(除了日文依旧看不懂)。然后在测试找bug的过程中,也学习了一些adb命令,比如查看当前界面对应的activity:adb shell dumpsys activity | findstr "mFocusedActivity"。通过这些命令的学习,我对项目有了更深的了解。 总结 以前只会用git管理代码,入职后对SVN的使用有了一定的了解,包括update、commit、diff、merge代码等。 对上手一个处于开发中的大型项目有了一定的经验 学会了更多adb命令的使用 adb shell dumpsys activity | findstr “mFocusedActivity” //获取当前界面的activity信息 adb shell input text “it’s text” //用adb命令输入文字 adb shell input keyevent 131 //左菜单键 adb shell input keyevent 132 //右菜单键 adb shell input keyevent 4 //返回键 adb install -r “apk路径” //-r 表示强制安装 adb uninstall -k “apk包名” //-k 表示卸载软件,但是保留配置和缓存文件 adb force-stop “apk包名” //强制停止应用]]></content>
<categories>
<category>work</category>
</categories>
<tags>
<tag>实习</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Handler Looper Messagequeue 间的关系]]></title>
<url>%2F2016%2F06%2F05%2Fhandler-looper-messagequeue%2F</url>
<content type="text"><![CDATA[前言出于性能考虑,Android 中的 UI 操作为非线程安全的,这意味着如果有多个线程并发操作 UI 组件,将会导致性能安全问题。为了解决这个问题,Android 制定了一条规则:只允许在 UI 线程中更新 UI。因此,Handler 类应运而生。 Handler 消息传递机制Handler 类的主要作用有两个: 在新启动的线程中发送消息 在主线程中接收消息,获取消息中数据以更新 UI 原理当新启动的线程发送消息的时候,消息会发送到与之关联的 MessageQueue 中,而 Handler 会不断地从 MessageQueue 中获取并处理消息–这会导致 Handler 类中处理消息的方法被回调。 下面是关于 Handler 的应用,每隔 1s 更新 TextView 上的时间。1234567891011121314151617181920212223242526272829303132333435public class MainActivity extends Activity{ private TextView textView; static Handler handler = new Handler{ int count; @Override public void handleMessage(Message msg){ switch(msg.what){ case 1: count++; textView.setText(count + "") break; default: break; } } } public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); textView = (TextView)findViewById(R.id.textView); new Timer().schedule(new TimerTask(){ @Override public void run(){ Message msg = Message.obtain(); msg.what = 1; handler.sendMessage(msg); } }, 0, 1000); }} 这里要注意的是,TimerTask 对象的本质就是开启一条新的线程。 Handler Looper MessageQueue 的工作原理先介绍一下与 Handler 一起工作的几个组件: Message: Handler 接收和处理消息的对象 Looper: 每个线程只能拥有一个 Looper, 它的 loop 方法负责读取 MessageQueue 中的消息,读到消息之后就把消息发送给该消息的 Handler 进行处理。 MessageQueue: 消息队列采用先进先出的方式管理消息。程序创建 Looper 对象时,会在它的构造器中创建 MessageQueue 对象。下面是 Looper 的构造函数源码: 12345private Looper(){ mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread();} 该构造函数采用 private 修饰,表明我们无法通过构造器来创建 Looper 对象。从上面代码中可以看出,程序在初始化 Looper 对象时会创建一个与之关联的 MessageQueue。 另外,由于 MessageQueue 是由 Looper 对象负责管理的,也就是说,如果希望 Handler 正常工作,必须在当前线程中有一个 Looper 对象。 在主线程中,系统已经初始化了一个 Looper 对象,因此程序直接创建 Handler 对象即可,然后就可以通过 Handler 来发送 处理消息了。 在子线程中,必须自己创建一个 Looper 对象,并启动它。创建 Looper 对象调用它的 prepare 方法即可(prepare 方法保证每个线程最多只有一个 Looper 对象),然后调用 Looper 的静态 loop 方法来启动它。loop 方法使用一个死循环不断地从MessageQueue 中取消息,并将取出的消息分发给该消息对应的 Handler 处理。 归纳起来,Looper MessageQueue Handler 各自的作用如下: Looper: 每个线程只能拥有一个 Looper, 它的 loop 方法负责读取 MessageQueue 中的消息,读到消息之后就把消息发送给该消息所属的 Handler 进行处理。 MessageQueue: 由 Looper 进行管理,它采用先进先出的方式管理 Message。 Handler: 它把消息发送给 Looper 管理的 MessageQueue,并负责处理 Looper 分发给它的消息。 在子线程中使用 Handler 的步骤如下: 调用 Looper 对象的 prepare 方法为当前线程创建 Looper 对象。创建 Looper 对象时,它的构造器会创建与之配套的 MessageQueue。 有了 Looper 对象后,创建 Handler 类的实例,重写 handleMessage 方法,该方法负责处理来自于其它线程的消息。 调用 Looper 的 loop 方法启动 Looper。 示例代码如下:12345678910111213141516171819202122232425262728293031323334public class MainActivity extends Activity{ private MyThread myThread; public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); myThread = new MyThread(); myThread.start(); } //处理点击事件 发送消息 public void click(View view){ Message msg = Message.obtain(); msg.what = 6; myThread.handler.sendMessage(msg); } class MyThread extends Thread{ public Handler handler; @Override public void run(){ Looper.prepare(); handler = new Handler(){ @Override public void handleMessage(Message msg){ //处理消息事件 } }; Looper.loop(); } }}]]></content>
<categories>
<category>android</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[音乐让我说]]></title>
<url>%2F2016%2F05%2F25%2Flet-me-say-about-music%2F</url>
<content type="text"><![CDATA[爱音乐 爱生活曾经单曲循环了很久的那些歌:《夏天Alex-不再联系》《颜小健-我以为还差一首抒情歌》《曾轶可-有可能的夜晚》,不是因为旋律多好听,再或者是歌词道出了心声,而仅仅是因为当时的环境、那样的心情,让人深陷其中。 每天傍晚从防空洞吃完饭回来,总是很享受那段路。听着广播台里放着悠扬的音乐,渐行渐远的路上,声音源也从左耳切换到了右耳,很享受那短短的几秒钟,视听的转换真的很让人震撼,原来音乐还可以这样听。 很多年后阳光照进回忆里,这种感觉,特别好。别等,自己想要的东西,自己去追求得到,大概会成为一生的信念。]]></content>
<categories>
<category>随笔</category>
</categories>
<tags>
<tag>music</tag>
</tags>
</entry>
<entry>
<title><![CDATA[自定义 Android 日志工具]]></title>
<url>%2F2016%2F05%2F24%2Ftools-of-log%2F</url>
<content type="text"><![CDATA[LogUtils在实际开发中,当应用程序发布时,我们往往需要清除程序中打印的日志。一方面是减少资源的消耗,另一方面我们不希望让用户看到一些用于测试的敏感数据。而一行行的删既费时又费力,而且也有可能没有完全清除。此外,清除日志后对应用后继的维护与开发带来不便,因为我们也许还会需要那些打印出来的日志。此工具类则解决这样一个问题:当应用需要发布的时候,只要将LEVEL_STATUS改为LEVEL_NONE,日志信息就会被屏蔽。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778import android.util.Log;/** * 此工具类对系统提供的打印日志类Log进行了封装 * Created by Jockio on 2016/5/24 */public class LogUtils { private final static int LEVEL_NONE = 0; private final static int LEVEL_VERBOSE = 1; private final static int LEVEL_DEBUG = 2; private final static int LEVEL_INFO = 3; private final static int LEVEL_WARN = 4; private final static int LEVEL_ERROR = 5; private static String TAG = "LogUtils"; //指示当前的打印状态 private static int LEVEL_STATUS = LEVEL_ERROR; public static void v(String tag, String msg){ if(LEVEL_STATUS >= LEVEL_VERBOSE){ Log.v(tag, msg); } } public static void d(String tag, String msg){ if(LEVEL_STATUS >= LEVEL_DEBUG){ Log.d(tag, msg); } } public static void i(String tag, String msg){ if(LEVEL_STATUS >= LEVEL_INFO){ Log.i(tag, msg); } } public static void w(String tag, String msg){ if(LEVEL_STATUS >= LEVEL_WARN){ Log.w(tag, msg); } } public static void e(String tag, String msg){ if(LEVEL_STATUS >= LEVEL_ERROR){ Log.e(tag, msg); } } public static void v(String msg){ if(LEVEL_STATUS >= LEVEL_VERBOSE){ Log.v(TAG, msg); } } public static void d(String msg){ if(LEVEL_STATUS >= LEVEL_DEBUG){ Log.d(TAG, msg); } } public static void i(String msg){ if(LEVEL_STATUS >= LEVEL_INFO){ Log.i(TAG, msg); } } public static void w(String msg){ if(LEVEL_STATUS >= LEVEL_WARN){ Log.w(TAG, msg); } } public static void e(String msg){ if(LEVEL_STATUS >= LEVEL_ERROR){ Log.e(TAG, msg); } }}]]></content>
<categories>
<category>android</category>
<category>tech</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 十一 - Fragment 与 动画]]></title>
<url>%2F2016%2F05%2F22%2Fandroid-note-day11%2F</url>
<content type="text"><![CDATA[Fragment 生命周期Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。官网这张图很好的说明了两者生命周期的关系: 可以看到Fragment比Activity多了几个额外的生命周期回调方法:onAttach(Activity)当Fragment与Activity发生关联时调用。onCreateView(LayoutInflater, ViewGroup,Bundle)创建该Fragment的视图onActivityCreated(Bundle)当Activity的onCreate方法返回时调用onDestoryView()与onCreateView想对应,当该Fragment的视图被移除时调用onDetach()与onAttach相对应,当Fragment与Activity关联被取消时调用注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现, Fragment123456789101112131415161718192021222324252627public class Fragment1 extends Fragment{ TextView tv; EditText et; Button button; //返回的View对象会作为fragment1的内容显示在屏幕上 @Override public view onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ View view = inflater.inflate(R.layout.fragment1, null); tv = (TextView)view.findViewById(R.id.textview); et = (EditText)view.findViewById(R.id.edittext); button = (Button)view.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ String text = et.getText().toString(); ((MainActivity)getActivity()).setText(text); } }); return view; } public void setText(String text){ tv.setText(text); }} 1234567891011121314151617class MainActivity extends Activity{ TextView tv; public void click(View v){ //把fragment界面显示在帧布局中 Fragment1 fg1 = new Fragment1(); FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); //资源id: 要显示布局的id ft.replace(R.id.fl, fg1); ft.commit(); } public void setText(String text){ tv.setText(text); }} 帧动画多张图片快速切换,形成动画效果 资源文件 frameanimation.xml123456789101112<!--false 表示会循环播放--><animation-list android:oneshot="false"> <item android:drawable="@drawable/pic1" android:duration="200" /> <item android:drawable="@drawable/pic2" android:duration="200" /> <item android:drawable="@drawable/pic3" android:duration="200" /> <item android:drawable="@drawable/pic4" android:duration="200" /> <item android:drawable="@drawable/pic5" android:duration="200" /> <item android:drawable="@drawable/pic6" android:duration="200" /> <item android:drawable="@drawable/pic7" android:duration="200" /> <item android:drawable="@drawable/pic8" android:duration="200" /> <item android:drawable="@drawable/pic9" android:duration="200" /></animation-list> 1234567891011121314public class MainActivity extends Activity{ ImageView iv; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); iv = (ImageView)findViewById(R.id.imageView); //把帧动画的资源文件指定为iv的背景 iv.setBackgroundResource(R.drawable.frameanimation); AnimationDrawable ad = (AnimationDrawable)iv.getBackground(); ad.start(); }} 补间动画组件由原始状态向终极状态转变时,为了让过渡更自然而自动生成的动画 平移动画1234567891011121314151617181920212223242526272829303132//平移public void translate(View v){ //定义位移动画 /* 10: 表示 x 坐标起始位置 imageView 的真实 x 坐标 + 10 100: 表示 x 坐标的结束位置 20: 表示 y 坐标的起始位置 200: 表示 y 坐标的结束位置 */ TranslateAnimation ta = new TranslateAnimation( 10, 100, 20, 200); /* Animation.RELATIVE_TO_SELF, 1: x 坐标的起始位置 imageView 的真实 x + 1 * imageView 的宽度 Animation.RELATIVE_TO_SELF, 0.5f: y 坐标的起始位置 imageView 的真实 y + 0.5 * imageView 的高度 */ TranslateAnimation ta = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 3, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2); //设置播放时间 ta.setDuration(2000); //会播放两次 ta.setRepeatCount(1); //重复的那一次动画的模式 ta.setRepeatMode(Animation.REVERSE); imageView.startAnimation(ta);} 缩放1234567891011121314151617181920//缩放public void scale(View v){ /* 0.5f: 表示 x 坐标缩放的初始位置 0.5 * imageView 的宽度 2: 表示 x 坐标缩放的结束位置 2 * imageView 的宽度 */ ScaleAnimation sa = new ScaleAnimation(0.5f, 2, 0.1f, 3); //设置缩放的中心点 ScaleAnimation sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, imageView.getWidth() / 2, imageView.getHeight() / 2); sa.setDuration(2000); sa.setRepeatCount(1); sa.setRepeatMode(Animation.REVERSE); //填充动画的结束位置 sa.setFillAfter(true); imageView.startAnimation(sa);} 透明12345public void alpha(View v){ AlphaAnimation aa = new AlphaAnimation(0, 0.5f); aa.setDuration(2000); imageView.startAnimation(aa);} 旋转1234567public void rotate(View v){ //相对中心点,从20度转到180度 RotateAnimation ra = new RotateAnimation(20, 180, imageView.getWidth() / 2, imageView.getHeight() / 2); ra.setDuration(2000); imageView.startAnimation(ra);} 补间动画集合12345678910111213public void fly(View v){ AlphaAnimation aa = new AlphaAnimation(0, 0.5f); aa.setDuration(2000); RotateAnimation ra = new RotateAnimation(20, 180, imageView.getWidth() / 2, imageView.getHeight() / 2); ra.setDuration(2000); AnimationSet set = new AnimationSet(false); set.add(aa); set.add(ra); imageView.startAnimation(set);} 属性动画:3.0 之后新特性补间动画并没有改变控件的真实位置,只是重新绘制了界面123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354public void translate(View v){ TranslateAnimation ta = new TranslateAnimation(0, 150, 0, 0); ta.setDuration(2000); ta.setFillAfter(true); imageView.startAnimation(ta); //target: 动画作用于哪一个组件 //改变的是控件的哪一个属性 //都是经过哪些 x 坐标点 ObjectAnimator oa = ObjectAnimator.ofFloat(imageView, "translationX", 10, 50, 20, 100); oa.setDuration(2000); oa.setRepeatCount(1); oa.setRepeatMode(ValueAnimator.REVERSE); oa.start();}//缩放public void scale(View v){ ObjectAnimator oa = ObjectAnimator.ofFloat(imageView, "scaleX", 1, 1.6f, 1.2f, 2); oa.setDuration(2000); oa.start();}//透明public void alpha(View v){ ObjectAnimator oa = ObjectAnimator.ofFloat(imageView, "alpha", 0, 0.6f, 0.2f, 1); oa.setDuration(2000); oa.start();}//旋转public void rotate(View v){ ObjectAnimator oa = ObjectAnimator.ofFloat(imageView, "rotation", 0, 90, 180, 90, 360); oa.setDuration(2000); oa.start();}//属性动画集合public void fly(View v){ AnimatorSet set = new AnimatorSet(); ObjectAnimator oa1 = ObjectAnimator.ofFloat(imageView, "rotation", 0, 90, 180, 90, 360); oa1.setDuration(2000); ObjectAnimator oa2 = ObjectAnimator.ofFloat(imageView, "alpha", 0, 0.6f, 0.2f, 1); oa2.setDuration(2000); //按顺序执行 set.playSequentially(oa1, oa2); //所有动画一起执行 //set.playTogether(oa1, oa2); set.start();} 使用xml定义属性动画资源文件类型:Property Animation资源文件名 objanimator.xml12345678910<?xml version="1.0" encoding="utf-8"?><set> <objectAnimator android:propertyName="translationX" android:duration="2000" android:repeatCount="1" android:repeatMode="reverse" android:valueFrom="-100" android:valueTo="100"/></set> 12345public void xml(View v){ Animator at = AnimatorInflater.loadAnimator(this, R.animator.objanimator); at.setTarget(imageView); at.start();}]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 十 - 内容提供者]]></title>
<url>%2F2016%2F05%2F21%2Fandroid-note-day10%2F</url>
<content type="text"><![CDATA[ContentProvider 内容提供者作用:把私有数据暴露给其它应用,通常是把私有数据库的数据暴露给其它应用 ContentProvider作为安卓的四大组件之一,使用时首先要在配置清单文件中声明。如果ContentProvider在清单文件中声明了权限,则同时要在配置清单中定义这个权限,同样,内容访问者要访问这个provider要首先声明相应的使用权限。 在application节点下添加以下内容:1234567<provider //其它应用访问内容提供者的地址 android:authorities="自定义唯一识别码" android:name="包名+类名" android:exported="true" //若为false,则不允许其他应用访问 android:readPermission="自定义一"//读权限 android:writePermission="自定义二"/>//写权限 若provider声明了权限,则要在Manifest节点下定义相应的权限12<permission android:name="自定义一"/><permission android:name="自定义二"/> 自定义ContentProvider继承自ContentProvider,实现父类的方法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394final static int INSERT = 1;final static int DELETE = 2;final static int UPDATE = 3;final static int QUERYALL = 4;final static int QUERYITEM = 5;//自定义内容提供者唯一标识符static String authorities = "com.example.myProvider";static UriMatcher uriMatcher;MyOpehHelper mOpenHelper;SQLiteDatabase db;static{ //检测其它用户传入的Uri与匹配器定义好的uri中,哪条匹配,不匹配返回 NO_MATCH uriMatcher=new UriMatcher(UriMatcher.NO_MATCH) //向内容提供者添加访问的Uri,根据Uri的访问相应的数据。 //content://authorities/person/insert 相匹配的话 返回 INSERT uriMatcher.addUri(authorities, "person/insert", INSERT); uriMatcher.addUri(authorities, "person/delete", DELETE); uriMatcher.addUri(authorities, "person/update", UPDATE); uriMatcher.addUri(authorities, "person/queryAll", QUERYALL); //# 可以代表任意一个数字,匹配Uri的话返回相应的匹配码 uriMatcher.addURI(authorities, "person/queryItem/#", QUERYITEM);}@Overridepublic boolean onCreate() { mOpenHelper = new MyOpenHelper(getContext()); return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { db = mOpenHelper.getWritableDatabase(); //使用Uri匹配器匹配传入的uri,匹配的话会返回定义好的匹配码 switch(uriMatcher.match(uri)){ case QUERYALL: Cursor c = db.query("person", projection, selection, selectionArgs, null, null, sortOrder); return c; case QUERYITEM://查询单条数据 if(db.isOpen()){ //把Uri末尾携带的数据取出来 long id = ContentUris.parseId(uri); Cursor cursor = db.query("person", projection, "_id=?", new String[]{id + ""}, null, null, sortOrder); return cursor; } return null; default: throw new RuntimeException("Uri not match"); }}@Overridepublic String getType(Uri uri) { switch (uriMatcher.match(uri)){ case QUERYALL: return "vnd.android.cursor.dir/person"; case QUERYITEM: return "vnd.android.cursor.item/person"; default: break; } return null;}//此方法供其它应用调用,用于往数据库中插入数据//values: 由其它应用传入,用于封装要插入的数据//uri: 内容提供者的访问地址 @Overridepublic Uri insert(Uri uri, ContentValues values) { db = mOpenHelper.getWritableDatabase(); //使用Uri匹配器匹配传入的uri,匹配的话会返回定义好的匹配码 switch(uriMatcher.match(uri)){ case INSERT: long id = db.insert("person", null, values); return ContentUris.withAppendedId(uri, id); default: throw new RuntimeException("Uri not match"); }}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs){ int i = db.delete("person", selection, selectionArgs); return i;}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs){ int i = db.update("person", values, selection, selectionArgs); return i;} ContentResolver 内容访问者(访问内容提供者)若要访问的内容提供者需要权限,则要在配置文件中声明相应的权限。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566private ContentResolver resolver;private Uri uri;//要访问的内容提供者的Uri前缀String authorities = "content://com.example.myProvider";@Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); resolver=getContentResolver(); //insert(); //delete(); //update(); queryAll(); queryItem();}public void insert(){ ContentValues values = new ContentValues(); values.put("name", "Jack"); values.put("money", "13000"); //url: 访问内容提供者的向数据库中插入数据的地址 resolver.insert(Uri.parse(authorities + "/person/insert"), values);;}public void delete(){ resolver.delete(Uri.parse("content://内容提供者的唯一标识符"), "name = ?", new String[]{"Jack"});}public void update(){ ContentValues values = new ContentValues(); values.put("name", "hello"); resolver.update(Uri.parse("content://内容提供者的唯一标识符"), values, "name = ?", new String[]{"Jack"});}public void queryAll(){ uri = Uri.parse("content://com.example.myProvider/person/queryAll"); Cursor c = resolver.query(uri,new String[]{"name", "age"}, null, null, "age desc"); if(c != null && c.getCount() > 0) { while (c.moveToNext()) { Log.i("query from resolver", c.getString(0) + " " + c.getInt(1)); } c.close(); }}public void queryItem(){ uri = Uri.parse("content://com.example.myProvider/person/queryItem/#"); //在Uri末尾添加一个id,把Uri末尾的#替换为id uri = ContentUris.withAppendedId(uri,1); Cursor c = resolver.query(uri, new String[]{"_id", "name", "age"}, null, null, "age desc"); if(c != null && c.moveToFirst()) { int id = c.getInt(0); String name = c.getString(1); int age = c.getInt(2); Log.i("queryItem from reslover", id + " " + name + " " + age); c.close(); }else { Log.i("queryItem from reslover", "null"); }} 备份短信1.清单文件中添加读取短信的权限2.定义一个短信的 Java Bean 类 Message1234567891011121314List<Message> smsList = new ArrayList<Message>();ContentResolver cr = getContentResolver();Uri uri = Uri.parse("content://sms");Cursor cursor = cr.query(uri, new String[]{"address", "date", "body", "type"}, null, null, null);while(cursor.moveToNext()){ String address = cursor.getString(0); long date = cursor.getLong(1); String body = cursor.getString(2); String type = cursor.getString(3); Message sms = new Message(); smsList.add(sms);} 插入短信清单文件中添加写短信与读短信的权限12345678ContentResolver cr = getContentResolver();ContentValues values = new ContentValues();values.put("address", 23423);values.put("type", 1);values.put("date", System.currentTimeMillis());values.put("body", "hello, hhhh");Uri uri = Uri.parse("content://sms");cr.insert(uri, valuse); 获取系统联系人 raw_contacts 表 contact_id: 联系人 id data 表 存放联系人的详细信息,每行数据存放联系人单独的一条信息 data1: 联系人具体信息 raw_contact_id: 该行信息属于哪个联系人 mimetype_id: 该行信息属于什么类型 注意:实际查询的时候,并不能直接查询mimetype_id字段,而是查询mimetype字段 mimetypes 表 对应类型的字符串 清单文件中添加相应的权限1234567891011121314151617181920212223242526ContentResolver cr = getContentResolver();String authorities = "content://com.android.contacts";Cursor cursorContactId = cr.query(Uri.parse(authorities + "/raw_contacts"), new String[]{"contact_id"}, null, null, null);while(cursorContactId.moveToNext()){ //获取联系人id String contactId = cursorContactId.getString(0); Cursor cursorData = cr.query(Uri.parse(authorities + "/data", new String[]{"data1", "raw_contact_id", "mimetype"}, "raw_contact_id = ?", new String[]{contactId}, null); //获取所有字段的名字 String[] names = cursorData.getColumnNames(); Person person = new Person(); whilw(cursorData.moveToNext()){ String data1 = cursorData.getString(0); String mimetype = cursorData.getString(1); if(mimetype.equals("vnd.android.cursor.item/email_v2")){ person.setEmail(data1); }else if(mimetype.equals("vnd.android.cursor.item/phone_v2")){ person.setPhone(data1); } else if(mimetype.equals("vnd.android.cursor.item/name")){ person.setName(data1); } }} 插入联系人清单文件中添加相应的权限1234567891011121314151617181920212223242526272829ContentResolver cr = getContentResolver();String authorities = "content://com.android.contacts";//先查询raw_contacts表,获取最新联系人的主键,然后主键+1,就是要插入联系人的idCursor cursorContactId = cr.query(Uri.parse(authorities + "/raw_contacts"), new String[]{"contact_id"}, null, null, null);//默认联系人id为1int contact_id = 1;while(cursorContactId.moveToLast()){ int _id = cursorContactId.getInt(0); contact_id = ++_id;}ContentValues values = new ContentValues();values.put("contact_id", contact_id);cr.insert(Uri.parse(authorities + "/raw_contacts"), values);values.clear();values.put("data1", "Jack");values.put("raw_contact_id", contact_id);values.put("mimetype", "vnd.android.cursor.item/name");values.clear();values.put("data1", "hello@163.com");values.put("raw_contact_id", contact_id);values.put("mimetype", "vnd.android.cursor.item/email_v2");values.clear();values.put("data1", "2453254532");values.put("raw_contact_id", contact_id);values.put("mimetype", "vnd.android.cursor.item/phone_v2");cr.insert(Uri.parse(authorities + "/data"), values); 注册内容观察者123456789101112131415161718ContentResolver cr = getContentResolver();//notifyForDescendents: 如果是true,那么只要以 content://sms 开头的uri的数据改变,都能收到通知。 //比如 content://sms/inboxcr.registerContentObserver(Uri.parse("content://sms"), true, new MyObserver(new Handler()));class MyObserver extends ContentObserver{ public MyObserver(Handler handler){ super(handler); } //收到数据改变的通知,调用此方法 @Override public void onChange(boolean selfChange){ super.onChange(selfChange); //读取短信数据库 }} 内容提供者中发送数据改变的通知插入、删除、更新等数据改变的操作,发送数据改变的通知12//uri: 通知发送到哪一个uri上,所有注册在这个uri上的内容观察者都可以收到这个通知getContext().getContentResolver().notifyChange(uri, null);]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[用服务打造自定义音乐播放器]]></title>
<url>%2F2016%2F05%2F20%2Fmy-music-player%2F</url>
<content type="text"><![CDATA[自定义音乐播放器本节通过服务与 SeekBar 的使用完成自定义音乐播放器项目通过在 MusicService.java 文件中指定要播放音乐的路径,来选择播放网络上或者本地存储中的音乐。注意:如果播放的是网络上的音乐,不要忘记在清单文件中添加访问网络的权限哦~ 布局文件activity_main.xml123456789101112131415161718192021222324252627282930313233343536373839<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical"> <Button android:id="@+id/play_button" style="@style/MyButtonStyle" android:text="@string/play" android:onClick="play"/> <Button android:id="@+id/pause_button" style="@style/MyButtonStyle" android:text="@string/pause" android:onClick="pause"/> <Button android:id="@+id/continue_button" style="@style/MyButtonStyle" android:text="@string/continue_button" android:onClick="continuePlay"/> <TextView android:id="@+id/time_textview" style="@style/MyButtonStyle" android:text="time"/> <SeekBar android:id="@+id/seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" /></LinearLayout> 主界面MainActivity.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.SeekBar;import android.widget.TextView;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;public class MainActivity extends AppCompatActivity { static SeekBar seekBar; static TextView timeTextView; static Calendar calendar; static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); MusicInterface musicInterface; Intent intent; MyServiceConnection conn; static Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Bundle bundle = msg.getData(); int max = bundle.getInt("max"); int position = bundle.getInt("position"); seekBar.setMax(max); seekBar.setProgress(position); //将毫秒转为相应的时间 calendar = Calendar.getInstance(); calendar.setTimeInMillis(max); Date date = calendar.getTime(); String total = simpleDateFormat.format(date); calendar.setTimeInMillis(position); date = calendar.getTime(); simpleDateFormat = new SimpleDateFormat("mm:ss"); String current = simpleDateFormat.format(date); timeTextView.setText(current + "/" + total); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); intent = new Intent(this, MusicService.class); startService(intent); conn = new MyServiceConnection(); bindService(intent, conn, BIND_AUTO_CREATE); } public void initView(){ seekBar = (SeekBar) findViewById(R.id.seekbar); seekBar.setOnSeekBarChangeListener(new MyOnSeekBarChangeListener()); timeTextView= (TextView) findViewById(R.id.time_textview); } class MyOnSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener{ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } /** * 当滑动seekBar松开手后触发的事件 * @param seekBar */ @Override public void onStopTrackingTouch(SeekBar seekBar) { int position = seekBar.getProgress(); musicInterface.seekTo(position); } } class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { musicInterface = (MusicInterface) service; } @Override public void onServiceDisconnected(ComponentName name) { } } /** * 开始播放音乐 * @param v */ public void play(View v){ musicInterface.play(); } /** * 暂停播放 * @param v */ public void pause(View v){ musicInterface.pause(); } /** * 继续播放 * @param v */ public void continuePlay(View v){ musicInterface.continuePlay(); } /** * 进程销毁后,解绑服务,停止服务 */ @Override protected void onDestroy() { super.onDestroy(); if(conn != null) { unbindService(conn); stopService(intent); } }} 自定义中间人接口MusicInterface.java123456public interface MusicInterface { void play(); void pause(); void continuePlay(); void seekTo(int position);} 自定义服务MusicService.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124import android.app.Service;import android.content.Intent;import android.media.MediaPlayer;import android.os.Binder;import android.os.Bundle;import android.os.IBinder;import android.os.Message;import java.io.IOException;import java.util.Timer;import java.util.TimerTask;public class MusicService extends Service { MediaPlayer player; Timer timer; @Override public void onCreate() { super.onCreate(); player = new MediaPlayer(); } @Override public void onDestroy() { super.onDestroy(); if(player != null) { player.stop(); player.release(); player = null; } if(timer != null){ timer.cancel(); timer = null; } } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return new MusicController(); } class MusicController extends Binder implements MusicInterface{ @Override public void play() { MusicService.this.play(); } @Override public void pause() { MusicService.this.pause(); } @Override public void continuePlay() { MusicService.this.continuePlay(); } @Override public void seekTo(int position) { MusicService.this.seekTo(position); } } public void play(){ if(player != null) { player.reset(); try { //播放本地音乐 String path = getFilesDir() + "/gaobie.mp3"; //播放网络音乐 //String path = "http://www.hello.com/a.mp3"; player.setDataSource(path); player.prepareAsync(); player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mp.start(); addTimer(); } }); } catch (IOException e) { e.printStackTrace(); } } } public void pause(){ player.pause(); } public void continuePlay(){ player.start(); } /** * 播放指定位置的音乐 * @param position */ public void seekTo(int position){ player.seekTo(position); } /** * 添加计时器,周期性的执行run方法 */ public void addTimer(){ if(timer == null){ timer = new Timer(); } timer.schedule(new TimerTask() { @Override public void run() { Message msg = Message.obtain(); Bundle bundle = new Bundle(); bundle.putInt("max", player.getDuration()); bundle.putInt("position", player.getCurrentPosition()); msg.setData(bundle); MainActivity.handler.sendMessage(msg); } }, 5, 500); }} 最终效果]]></content>
<categories>
<category>android</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 九 - MediaPlayer]]></title>
<url>%2F2016%2F05%2F20%2Fandroid-note-day09%2F</url>
<content type="text"><![CDATA[图片大小的计算:图片的像素 * 每个像素所占的大小 单色位图:只能表示两种颜色,使用两个数字 0 与 1 表示,使用一个长度为 1 的二进制数组就可以表示了,每个像素占用 1/8 个字节 16 色位图:能表示 16 种颜色,需要 16 个数字, 0 - 15, 0000 - 1111 使用一个长度为 4 的二进制数组就能够表示了,每个像素占用 1/2 个字节 256 色位图:能表示 256 种颜色 需要 256 个数字:0 - 255, 0000 0000 - 1111 1111 使用一个长度为 8 的二进制数字 每个像素占用 1 个字节 24 位位图 每个像素占用 24 位,也就是 3 个字节,所以叫 24 位位图 R: 0 - 255 G: 0 - 255 B: 0 - 255 利用缩放加载大图片计算机把图片所有像素信息全部解析出来,保存至内存Android 保存图片像素信息,是用 ARGB 保存的,每个像素占用 4 个字节手机屏幕 320 x 480,总像素 153600图片宽高 2400 x 3200,总像素 76800002400 / 320 = 73200 / 480 = 6用大的数来缩放,这样才可以在屏幕上显示完整的图片123456789101112131415161718192021222324252627282930313233public void click(View v){ Options opt = new Options(); //不为像素申请内存,只获取图片宽高 opt.inJustDecodeBounds = true; BitmapFactory.decodeFile("/sdcaed/dog.jpg", opt); int width = opt.outWidth(); int height = opt.outHeight(); //获取屏幕宽高 Display dp = getWindowManager().getDefaultDisplay(); int screenWidth = dp.getWidth(); int screenHeight = dp.getHeight(); //api 13 才能使用 //dp.getSize(new Point()) //计算缩放比例 int scale = 1; int scaleWidth = width / screenWidth; int scaleHeight = height /screenHeight; scale = scaleWidth >= screenHeight ? scaleWidth : scaleHeight; if(scaleWidth >= scaleHeight && scaleWidth >= 1){ scale = scaleWidth; }else if(scaleWidth < scaleHeight && scaleHeight >= 1){ scale = scaleHeight; } //设置缩放比例 opt.inSampleSize = scale; opt.inJustDecodeBounds = false; Bitmap bm = BitmapFactory.decodeFile("/sdcaed/dog.jpg", opt); imageView.setImageBitmap(bm);} 创建图片副本在内存中创建一个图片的拷贝123456789101112//这个对象是只读的Bitmap bm = BitmapFactory.decodeFile("/sdcaed/dog.jpg");//创建图片副本//在内存中创建一个与原图一模一样大小的bitmap对象,里面还没有绘制任何内容//该对象可读可写Bitmap bmCopy = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), bm.getConfig());//创建画笔Paint paint = new Paint();//创建画板对象Canvas canvas = new Canvas(bmCopy);//开始绘制canvas.drawBitmap(bm, new Matrix(), paint); 简单特效处理123456789101112131415161718Matrix matrix = new Matrix();//平移 将顶点相对于bitmap平移至(20, 40)matrix.setTranslate(20, 40);//以图片右下角顶点缩放 宽放大两倍,高缩小到0.5倍matrix.setScale(2, 0.5f);//以图片中心点缩放 宽放大两倍,高缩小到0.5倍matrix.setScale(2, 0.5f, bm.getWidth() /2, bm.getHeight() / 2);//旋转 相对左上角matrix.setRotate(45);//旋转中心点在中心matrix.setRotate(45, bm.getWidth() /2, bm.getHeight() /2);//镜面效果matrix.setScale(-1, 1);matrix.postTranslate(bm.getWidth(), 0);//倒影效果matrix.setScale(1, -1);matrix.postTranslate(0, bm.getHeight()); 触摸事件:画板123456789101112131415161718192021222324252627282930313233343536373839Bitmap bm = BitmapFactory.decodeResources(getResources(), R.drawable.bg);Bitmap bmCopy = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), bm.getConfig());Paint paint = new Paint();paint.setColor(Color.RED);paint.setStrokeWidth(4);//创建画板对象Canvas canvas = new Canvas(bmCopy);//开始绘制canvas.drawBitmap(bm, new Matrix(), paint);imageView.setImageBitmap(bmCopy);imageView.setOnTouchListener(new OnTouchListener(){ int startX; int startY; @Override public boolean onTouch(View v, MotionEvent event){ int action = event.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: startX = (int)event.getX(); startY = (int)event.getY(); break; case MotionEvent.ACTION_MOVE: int x = (int)event.getX(); int y = (int)event.getY(); canvas.drawLine(startX, startY, x, y, paint); imageView.setImageBitmap(bmCopy); startX = x; startY = y; break; case MotionEvent.ACTION_UP: break; } //返回 true: 告诉系统,这个触摸事件由我处理 //返回 false: 告诉系统,这个事件我不处理,这时系统会把触摸事件传递给 imageView 的父节点 return true; }}); 画板图片的保存SD卡每次准备的时候,系统会遍历SD卡中的所有文件,系统会把所有的多媒体文件都放在一个MediaStore数据库中生成一个索引,数据库中保存了文件的文件名、路径、大小、长度、艺术家等。图库、音乐、视频每次启动时,其实不会去遍历SD卡寻找多媒体文件,而是从MediaStore数据库中读取多媒体文件,通过库中的索引找到相对应的多媒体文件后,把文件显示在界面12345678File file = new File(getFilesDir(), "zuopin.png");FileOutputStream fos = new FileOutputStream(file);bmCopy.compress(CompressFormat.PNG, 100, fos);Intent intent = new Intent();intent.setAction(Intent.ACTION_MEDIA_MOUNTED);intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory()));sendBroadcast(intent); 播放网络音乐123456interface MusicInterface{ void play(); void pause(); void continuePlay(); void seekTo(int position);} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798class MusicService extends Service{ MediaPlayer mediaPlayer; Timer timer; @Override public IBinder onBind(Intent intent){ return new MusicController(); } @Override public void onCreate(){ mediaPlayer = new MediaPlayer(); } @Override public void onDestory(){ super.onDestory(); mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; if(timer != null){ timer.cancel(); timer = null; } } class MusicController extends Binder implements MusicInterface{ @Override public void play(){ MusicService.this.play(); } @Override public void pause(){ MusicService.this.pause(); } @Override public void continuePlay(){ MusicService.this.continuePlay(); } @Override public void seekTo(int position){ MusicService.this.seekTo(position); } } public void play(){ musicPlayer.reset(); try{ //播放本地音乐 //musicPlayer.setDataSource(getFilesDir + "/a.mp3"); //musicPlayer.prepare(); //musicPlayer.start(); //播放网络音乐 musicPlayer.setDataSource("http://hello.com/bzd.mp3"); musicPlayer.prepareAsync(); musicPlayer.setOnPreparedListener(new OnPreparedListener(){ @Override public void onPrepared(MediaPlayer player){ player.start(); addTimer(); } }); }catch(Exception e){ e.printStackTrace(); } } public void continuePlay(){ musicPlayer.start(); } public void pause(){ musicPlayer.pause(); } public void seekTo(int position){ musicPlayer.seekTo(position); } public void addTimer(){ if(timer == null) timer = new Timer(); timer.schedule(new TimerTask(){ //获取歌曲总时长 int duration = musicPlayer.getDuration(); //获取歌曲当前播放进度 int position = musicPlayer.getCurrentPosition(); Message msg = Message.obtain(); Bundle bundle = new Bundle(); bundle.putInt("duration", duration); bundle.putInt("position", position); msg.setData(bundle); MainActivity.handler.sendMessage(msg) }, 5, 500);//开始计时任务后的5毫秒,第一次执行run方法,以后每500毫秒执行一次 }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879public class MainActivity extends Activity{ MusicInterface interface; MyServiceConnection conn; Intent intent; static SeekBar seekBar; static Handler handler =new Handler(){ public void handleMessage(Message msg){ Bundle bundle = msg.getData(); int duration = bundle.getInt("duration"); int position = bundle.getInt("position"); seekBar.setMax(duration); seekBar.setProgress(position); } } @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); seekBar = (SeekBar)findViewById(R.id.seekbar); seekBar.setOnSeekBarChangeListener(new MyOnSeekBarChangeListener()); intent = new Intent(this, MusicService.class); startService(intent); conn = new MyServiceConnection(); bindService(intent, conn, BIND_AUTO_CREATE); } class MyOnSeekBarChangeListener implements OnSeekBarChangeListener{ @Override public void onStopTrackingTouch(SeekBar seekBar){ //拖动seekBar后,改变播放进度 int position = seekBar.getProgress(); interface.seekTo(position); } @Override public void onStartTrackingTouch(SeekBar seekBar){ } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser){ } } class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service){ interface = (MusicInterface)service; } @Override public void onServiceDisconnected(ComponentName name){ } } public void play(View v){ interface.play(); } public void pause(View v){ interface.pause(); } public void continuePlay(View v){ interface.continuePlay(); } //退出应用 并销毁服务 public void exit(View v){ unbindService(conn); stopService(intent); }} 播放视频 MediaPlayer + SurfaceView双缓冲技术重量级组件只要不可见,就不会创建,可见时才会创建只要不可见,就会销毁123456789101112131415161718192021222324252627282930313233343536373839404142434445464748class MainActivity extends Activity{ MediaPlayer player; SurfaceView sv; static int currentPosition; protected coid onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); sv= (SurfaceView)findViewById(R.id.surfaceView); final SurfaceHolder sh = sv.getHolder(); sh.addCallback(new Callback(){ @Override public void surfacceDestoryed(SurfaceHolder holder){ if(plyer != null){ currentPosition = player.getCurrentPosition(); player.stop(); player.release(); player = null; } } @Override public void surfacceCreated(SurfaceHolder holder){ if(player == null){ player = new MediaPlayer(); player.reset(); try{ player.setDataSource(getFilesDir + "/a.mp4"); palyer.setDisplay(holder); player.prepare(); player.start(); player.seekTo(currentPosition); }catch(Exception e){ e.printStackTrace(); } } } @Override public void surfacceChanged(SurfaceHolder holder, int format, int width, int height){ } }); }} VideoView123//本地播放video.setVideoPath(getFilesDir + "/a.mp4");video.start(); FFMPEG开源免费的音视频编解码器 Vitamio 视频播放第三方框架封装了 FFMPEG 的视频播放框架对外提供的 API 全部都是 java api封装的有 VideoView1234567//检测硬件是否支持 Vitamio 引擎if(!LibsChecker.checkVitamioLibs(this)){ return;}video.setVideoPath(getFilesDir + "/a.rmvb");video.start();video.setMediaController(new MediaController(this)); 拍照与摄像1234567891011121314151617181920212223242526public image(View v){ Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); File file = new File(getFilesDir + "/a.jpg"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); startActivityForResult(intent, 10);}protected void onActivityResult(int requestCode, int responseCode, Intent data){ super.onActivityResult(requestCode, responseCode, intent); if(requestCode == 10){ //拍照成功 }else if(requestCode == 20){ //摄像成功 }public video(View v){ Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); File file = new File(getFilesDir + "/a.3gp"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); //第二个属性 0: 低质 1: 高质 intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); startActivityForResult(intent, 20);}]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 八 - 服务]]></title>
<url>%2F2016%2F05%2F19%2Fandroid-note-day08%2F</url>
<content type="text"><![CDATA[服务:长期后台运行的没有界面的组件服务的目的:长期后台运行系统不容易回收掉进程。即使回收了,内存充足的时候,会把进程重新创建。 创建服务1.创建服务,并在清单文件中配置服务123456class MyService extends Service{ @Override public IBinder onBind(Intent intent){ return null; }} 2.开启服务12Intent intent = new Intent(this, MyService.class);startService(intent); 3.关闭服务12Intent intent = new Intent(this, MyService.class);stopService(intent); 进程分为5个等级的优先级:(从高到低) 1.Foreground process 前台进程 用户正在玩的应用程序对应的进程2.Visible process 可视进程 用户仍然可以看到这个进程的页面3.Service process 服务进程 应用程序有一个服务组件在后台运行4.Background process 后台进程 应用程序没有服务在运行 并且最小化(activity onStop)5.Empty process 空进程 没有任何正在运行的activity 任务栈空了 android系统进程管理是按照一定的规则的:应用程序一旦被打开,通常情况下关闭后(清空任务栈)进程不会停止,方便下一次应用启动。android系统有一套内存清理机制,按照优先级回收系统的内存。 服务的生命周期 onCreate():服务第一次创建时调用onStartCommand():服务启动时调用onDestory():服务销毁时调用 电话录音机电话的状态1.空闲状态2.响铃状态3.摘机状态(接听)1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public class RecordService extends Service{ @Override public IBinder onBind(Intent intent){ return null; } @Override public void onCreate(){ super.onCreate(); TelephonyManager tm = (TelephonyManager)getSystemServie(TELEPHONY_SERVICE); //第二个参数决定监听什么内容 tm.listen(new MyListener(), PhoneStateListener.LISTEN_CALL_STATE); }}class MyListener extends PhoneStateListener{ private MediaRecorder recorder; @Override onCallStateChanged(int state, String incomingNumber){ super.onCallStateChanged(state, incomingNumber); switch(state){ case TelephonyManager.CALL_STATE_IDLE: if(recorder != null){ recorder.stop(); recorder.release(); recorder = null; } break; case TelephonyManager.CALL_STATE_RINGING: //初始化录音机 if(recorder == null){ recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutFormat(MediaRecorder.OutputFormat.THREE_GPP); recorder.setOutputFile(getFilesDir().getPath() + "/recorder.3gp"); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try{ recorder.prepare(); }catch(Exception e){ e.printStackTrace(); } } break; case TelephonyManager.CALL_STATE_OFFHOOK: //开始录音 recorder.start(); break; default: break; } }} 服务的两种启动方式及生命周期 startService(): 启动服务所在的进程属于服务进程activity一旦启动服务,服务就跟activity没有关系了onCreate()->onStartCommand()->onDestory() bindService(): 启动服务所在的进程不属于服务进程activity一旦与服务建立连接,activity销毁,服务也会销毁 自定义一个接口:1234interface Person{ //定义公共访问的方法 public void visitService();} 在Activity中代码:123456789101112131415161718192021222324252627282930313233343536373839MyServiceConnection conn;Intent intent;public void onCreate(Bundle onSavedInstanceStste){ super.onCreate(onSavedInstanceStste); setContentView(R.layout.main); intent = new Intent(this, MyService.class); conn = new MyServiceConnection(); bindService(intent, conn, BIND_AUTO_CREATE);}public void bind(View v){ //绑定服务 onCreate()->onBind() bindService(intent, conn, BIND_AUTO_CREATE);}public boolean unbind(View v){ //解绑服务 onUnbind()->onDestory() unbindService(conn);}Person p;public void click(View v){ p.visitService();}class MyServiceConnection implements ServiceConnection{ //连接服务成功,第二个参数即为中间人对象 @Override public void onServiceConnected(ComponentName name, IBinder service){ p = (Person)service; } @Override public void onServiceDisconnected(ComponentName name){ }} 自定义Service中代码:123456789101112131415161718192021222324252627class MyService extends Service{ //绑定时调用 @Override public IBinder onBind(Intent intent){ //返回一个Binder对象,即中间人对象 return new XiaoLi(); } //创建内部类作为中间人,来访问服务中方法 class XiaoLi extends Binder implements Person{ //实现接口中用于公共访问的方法 public void visitService(){ //访问service中的方法 help(); } //自己的方法,不让外界访问 public void daMaJiang(){ } } //自定义服务的方法 public void help(){ System.out.println("帮人办事"); }} 服务模拟音乐播放1234interface MusicInterface{ public void controlPlay(); public void controlPause();} 123456789101112131415161718192021222324252627282930313233343536class MainActivity extends Activity{ MusicServiceConnection conn; MusicInterface interface; Intent intent; public void onCreate(Bundle onSavedInstanceStste){ super.onCreate(onSavedInstanceStste); setContentView(R.layout.main); intent = new Intent(this, MusicService.class); conn = new MusicServiceConnection(); //混合调用,为了把服务所在进程变为服务进程 startService(intent); //为了拿到中间人对象 bindService(intent, conn, BIND_AUTO_CREATE); } public void play(View v){ interface.controlPlay(); } public void pause(View v){ interface.controlPause(); } class MusicServiceConnection implements ServiceConnection{ //连接服务成功,第二个参数即为中间人对象 @Override public void onServiceConnected(ComponentName name, IBinder service){ interface = (MusicInterface)service; } @Override public void onServiceDisconnected(ComponentName name){ } }} 12345678910111213141516171819202122232425class MusicService extends Service{ //绑定时调用 @Override public IBinder onBind(Intent intent){ //返回一个Binder对象,即中间人对象 return new MusicController(); } class MusicController extends Binder implements MusicInterface{ public void controlPlay(){ play(); } public void controlPause(){ pause(); } } public void play(){ System.out.println("开始播放音乐"); } public void pause(){ System.out.println("暂停播放音乐"); }} 服务的混合调用onCreate()->onStartCommand()->onBind()->onUnbind()->onDestory() 使用代码配置广播接收者 使用清单文件注册广播一旦发出,系统就会去所有清单文件中寻找哪个广播接收者的action和广播的action是匹配的。如果找到了,就会把该广播接收者的进程启动起来 使用代码注册需要使用广播接收者时,执行注册的代码;不需要时,解除注册 特殊的广播接收者安卓中有一些广播接收者,必须使用代码注册,清单文件注册是无效的1). 屏幕锁屏与解锁2). 电量改变 使用服务注册广播接收者12345678public class ScreenReceiver extends BroadcastReceiver{ public void onReceive(Context context, Intent intent){ String action = intent.getAction(); if(Intent.ACTION_SCREEN_ON,equals(action)){ //do something } }} 自定义服务代码中:123456789101112131415161718192021222324252627public MyService extends Service{ ScreenReceiver receiver; @Override public IBinder onBind(Intent intent){ return null; } @Override public void onCreate(){ super.onCreate(); //创建广播接收者 receiver = new ScreenReceiver(); //创建intent-filter IntentFilter filter= new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); //注册广播接收者 registerReceiver(receiver, filter); } @Override public void onDestory(){ super.onDestory(); unregisterReceiver(receiver); }} 服务的分类 本地服务 服务与启动服务的activity在同一个进程中 远程服务 服务与启动服务的activity不在同一个进程中 远程服务1234567891011121314151617public MemoteService extends Service{ @Override public IBinder onBind(Intent intent){ return null; } @Override public void onCreate(){ super.onCreate(); } @Override public void onDestory(){ super.onDestory(); }} 启动远程服务12345Intent intent = new Intent();intent.setAction("远程服务清单文件中配置的action");//android 5.0 之后需要指定要启动的应用程序包名intent.setPackage("com.jockio.learnandroid");startService(intent); AIDL: 进程间通信Android Interface Definition Language步骤: 将远程服务的方法抽取成一个单独的接口 java 文件 将接口文件的后缀名 java 改为 aidl, 在自动生成的接口 .java 文件中,有一个静态抽象类 Stub,它已经继承了 Binder 类,实现了抽取方法后的接口,这个类就是中间人 把 aidl 文件复制粘贴到要访问远程服务的项目中 注意:aidl 包名跟原包名必须完全一致 在要访问远程服务的项目中,强转中间人对象时,直接使用 Stub.asInterface( Service service) 用 AIDL 模拟支付宝服务PayInterface.aidl123interface PayInterface{ public void pay();} 123456789101112131415161718class PayService extends Service{ //绑定时调用 @Override public IBinder onBind(Intent intent){ //返回一个Binder对象,即中间人对象 return new PayController(); } class PayController extends Stub{ public void pay() throws RemoteException{ PayService.this.pay(); } } public void pay(){ System.out.println("完成支付"); }} 远程调用模拟支付宝服务1234567891011121314151617181920212223242526272829303132333435363738//把 PayInterface.aidl 文件复制粘贴到项目中//注意:PayInterface.aidl 包名跟原包名必须完全一致class MainActivity extends Activity{ PayServiceConnection conn; Intent intent; PayInterface interface; public void onCreate(Bundle onSavedInstanceStste){ super.onCreate(onSavedInstanceStste); setContentView(R.layout.main); intent = new Intent(); intent.setAction("支付宝服务的action"); //android 5.0 之后需要指定要启动的应用程序包名 intent.setPackage("com.jockio.learnandroid"); conn = new PayServiceConnection(); //混合调用,为了把服务所在进程变为服务进程 startService(intent); //为了拿到中间人对象 bindService(intent, conn, BIND_AUTO_CREATE); } public void click(View v){ interface.pay(); } class PayServiceConnection implements ServiceConnection{ //连接服务成功,第二个参数即为中间人对象 @Override public void onServiceConnected(ComponentName name, IBinder service){ interface = Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name){ } }} 进程优先级补充前台进程 拥有一个正在与用户进行交互的activity(onResume方法调用)的进程 拥有一个与正在和用户交互的activity绑定的服务的进程 拥有一个正在“运行于前台”的服务–服务的startForeground方法调用 拥有一个正在执行以下三个生命周期方法中任意一个的服务 onCreate() onStart() onDestory 拥有一个正在执行 onReceive 方法的广播接收者的进程 可见进程 拥有一个不在前台,但是用户依然可见的activity(onPause方法调用)的进程 拥有一个与可见(或前台)activity绑定的服务的进程 样式与主题资源目录下,在 values 文件夹中,新建 styles.xml 文件123456789101112131415<?xml version="1.0" encoding="utf-8"><resources> <style name="myStyle"> <item name="android:textSize">20sp</item> </style> <!--继承方式一--> <style name="textviewStyle" parent="myStyle"/> <!--继承方式二--> <style name="myStyle.another"/> <!--主题--> <style name="themeStyle"> <item name="android:background">#f0f0ff</item> </style></resources> 布局文件中123<TextView android:text="标题" style="@style/myStyle.another"/> 国际化新建文件夹 values-en, 新建strings.xml1234<?xml version="1.0" encoding="utf-8"><resources> <string name="app_name">hello</string></resources> 新建文件夹 values-zh, 新建strings.xml1234<?xml version="1.0" encoding="utf-8"><resources> <string name="app_name">你好</string></resources>]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 七 - 广播]]></title>
<url>%2F2016%2F05%2F18%2Fandroid-note-day07%2F</url>
<content type="text"><![CDATA[获取打电话的广播1.在清单文件中配置receiver,指定要接收广播的类型,并添加相应的权限。12345<receiver android:name = "包名+类名"> <intent-filter> <action android:name = "android.intent.action.NEW_OUTGOING_CALL"/>//外拨电话 </intent-filter></receiver> 2.创建一个类继承自BrocastReceiver 并重写onReceive()方法,该方法在接收到广播的时候调用1234//在打电话广播中,会携带拨打电话的号码String phoneNumber = getResultData();//获取数据//把新的号码放到广播中setResultData(phoneNumber); 即便广播接收者所在进程已经被关闭,当系统发送出的action跟该广播的action是匹配的,系统会启动该广播接收者所在的进程,并把广播发送给该广播接收者 监听短信1.在清单文件中配置广播,并添加相应权限12345<receiver android:name = "包名+类名"> <intent-filter> <action android:name = "android.provider.Telephony.SMS_RECEIVED"/> </intent-filter></receiver> 2.创建一个类继承自BrocastReceiver 并重写onReceive()方法,该方法在接收到广播的时候调用1234567Object[] objs=intent.getExtras().get("pdus");//获得一组短信for(Object obj:objs){ //得到短信对象 SmsMessage smsMessage=SmsMessage.createFromPdu((byte[])obj); String body=smsMessage.getMessageBody(); String sender=smsMessage.getOriginatingAddress();} 4.0之后,广播接收者所在进程如果从来没启动过,那么广播接收者不会生效4.0之后,如果系统自动关闭广播接收者所在进程,在广播中的action跟该广播接收者的action匹配时,系统会启动该广播接收者所在的进程,但是如果是用户手动关闭该进程,那么该进程会进入冻结状态,再也不会启动,直到下一次用户手动启动该进程。 监控SD卡状态1.在清单文件中配置广播,并添加相应权限12345678<receiver android:name = "包名+类名"> <intent-filter> <action android:name = "android.intent.action.MEDIA_MOUNTED"/> <action android:name = "android.intent.action.MEDIA_REMOVED"/> <action android:name = "android.intent.action.MEDIA_UNMOUNTED"/> <data android:scheme = "file"/> </intent-filter></receiver> 2.onReceive()12345//判断收到的是什么广播String action = intent.getAction();if(action.equals("android.intent.action.MEDIA_MOUNTED")){ Toast.makeText(context, "SD卡可用", Toast.LENGTH_SHORT).show();} 开机启动应用1.在清单文件中配置广播,并添加相应权限12345<receiver android:name = "包名+类名"> <intent-filter> <action android:name = "android.intent.action.BOOT_COMPLETED"/> </intent-filter></receiver> 2.onReceive()1234Intent it = new Intent(context, MainActivity.class);//创建任务栈,存放启动的activityit.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(it); 监控应用状态: 安装、更新、卸载1.在清单文件中配置广播,并添加相应权限12345678<receiver android:name = "包名+类名"> <intent-filter> <action android:name = "android.intent.action.PACKAGE_ADDED"/> <action android:name = "android.intent.action.PACKAGE_REPLACED"/> <action android:name = "android.intent.action.PACKAGE_REMOVED"/> <data android:scheme = "package"/> </intent-filter></receiver> 2.onReceive()123456//判断收到的是什么广播String action = intent.getAction();if(action.equals("android.intent.action.PACKAGE_ADDED")){ Uri uri = intent.getData(); Toast.makeText(context, uri.toString() + " 应用安装了", Toast.LENGTH_SHORT).show();} 自定义广播发送自定义广播一般都是隐式意图12345Intent intent = new Intent();//自定义intent.setAction("包名.动作");intent.putExtras("","");sendBroadcast(intent); 接收自定义广播清单文件中配置广播接收者12345<receiver android:name = "包名+类名"> <intent-filter> <action android:name = "包名.动作"/> </intent-filter></receiver> 发送无序广播没有顺序的广播,所有与广播中的action匹配的广播接收者都可以收到这条广播,并且是没有先后的顺序,视为同时收到sendBroadcast(intent);//无序广播 发送有序广播有顺序的广播,所有与广播中的action匹配的广播接收者都可以收到这条广播,但是是有先后的顺序,按照广播接收者的优先级排序有序广播可以被拦截,可被终止,可以被修改数据12345678910Intent intent = new Intent();intent.setAction("it.java.fdm");sendOrderedBroadcast(intent, null,//第二个参数为接收广播需要的权限 null,//最终的广播接收者,只接收该条广播并且一定可以收到,不需要在清单文件中配置 null, 0, "每人发100斤大米", null);//Bundle对象,携带数据sendBroadcast(intent); 有序广播接收者123456789101112131415<receiver android:name = "包名+类名"> <intent-filter android:priority = "1000"> <action android:name = "it.java.fdm"/> </intent-filter></receiver><receiver android:name = "包名+类名"> <intent-filter android:priority = "800"> <action android:name = "it.java.fdm"/> </intent-filter></receiver><receiver android:name = "包名+类名"> <intent-filter android:priority = "600"> <action android:name = "it.java.fdm"/> </intent-filter></receiver> onReceive()中:12String data = getResultData();//"每人发100斤大米"setResultData("每人发120斤大米");//修改数据]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 六]]></title>
<url>%2F2016%2F05%2F17%2Fandroid-note-day06%2F</url>
<content type="text"><![CDATA[页面跳转activity只要配置为启动界面的intent_filter,就会生成应用图标与名称隐式意图:通过指定的action与data显式意图:直接指定目标activity的包名和类名1234567<!--给activity配置intent-filter--><activity android:name=".mainActivity"> <intent-filter> <action android:name="com.java.intent"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter></activity> 隐式意图12345678910111213Intent intent = new Intent();intent.setAction("com.java.intent");//下面两行代码不能共存//intent.setData(Uri.parse(hello:123));//intent.setType("text/username");intent.setDataAndType(Uri.parse(hello:123), "text/username");//若没有配置category,系统会自动添加默认的categoryintent.setCategory(Intent.CATEGORY_DEFAULT);Bundle bundle = new Bundle();bundle.putString("username", name);bundle.putString("password", password);intent.putExtras(bundle);startActivity(intent); 跳转后,获取数据123456Intent intent = getIntent();Uri uri = intent.getData();String data = uri.toString();Bundle bundle = intent.getExtras();String username = bundle.get("username");String password = bundle.get("password"); 显式、隐式意图 应用场景 显式意图:启动用一个应用中的activity隐式意图:启动不同应用中的activity隐式意图效率要低于显式意图如果系统中有多个activity与意图设置的action相匹配,那么在启动activity时会弹出对话框让用户选择启动哪一个应用。 返回activity时传递数据应用场景:发送短信时,选择联系人12345678910111213141516Intent intent = new Intent();intent.putExtra("name", "Jack");//一旦这个activity被销毁,就会将结果返回给它的调用者,就是启动了这个activity的activitysetResult(0, intent);//调用方Intent intent = new Intent();intent.setClass(MainActivity.this, Activity_2.class);//第二个参数为请求码startActivityForResult(intent, 0);@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data){ super.onActivityResult(requestCode, resultCode, data); data.getStringExtra("name");} activity生命周期 完整生命周期:onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()可视生命周期:onStart()->onResume()->onPause()->onStop()前台生命周期:onResume()->onPause()后台进入前台:onRestart()->onStart()->onResume() 123456onCreate():被创建时调用的方法onStart():当activity界面用户可见时调用,但是没有获取焦点onResume():界面开始获取到焦点对应的方法。 (界面按钮可以被点击,文本框可以输入内容)onPause():界面失去焦点对应的方法(暂停)(按钮不可被点击,文本框不可输入内容,但是界面用户仍然能看见)onStop():当activity界面用户不可见时调用的方法onDestory():被销毁时调用的方法 使用场景:1.应用程序退出自动保存数据 ondestory oncreate2.应用程序最小化 暂停的操作 onstop onstart 视频播放器3.游戏的暂停和开始 前台生命周期 内存不足时 系统会优先杀死后台activity所在的进程,都杀光了,如果内存还是不足,那么就会杀死暂停状态的activity进程,如果还不够,有可能杀死前台进程。如果有多个后台进程,在选择杀死目标时,采用最近最少使用算法。 任务栈 任务栈(task stack) (别名 back stack 后退栈), 用来记录存放用户开启的activity,帮助维护好用户体验。 一个应用程序一被开启,系统就给他分配一个任务栈,当所有activity都退出的时候,任务栈就清空了。 任务栈的id(getTaskId())是Integer类型的,自增长的。 在 android 操作系统中会存在多个任务栈,一个应用程序一个任务栈。 默认情况下关闭掉一个应用程序,实际上是清空了这个应用程序的任务栈,应用程序的进程还会保留。 activity的启动模式 standard 默认的标准启动模式,每次startActivity都会创建一个新的activity的实例,按照后进先出的原则将activity添加到任务栈中,每次可见的activity都位于任务栈的栈顶。 singleTop 单一顶部,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用onNewIntent()方法。应用场景:浏览器书签。 singleTask 单一任务栈,activity只会在任务栈里面存在一个实例,如果要激活的activity在任务栈里面已经存在,则不会创建新的activity,而是复用这个已经存在的activity,调用onNewIntent()方法,并且清空这个activity任务栈上面所有的activity。应用场景:浏览器activity。整个任务栈只有一个实例,节约内存、CPU。注意:activity还是运行在当前应用程序的任务栈里面的 singleInstance 单一实例 在整个手机操作系统中,只有一个实例存在,不同的应用打开这个activity,共享的是同一个activity。它会运行在自己单独,独立的任务栈里面,并且任务栈里面只有它一个实例存在。 在manifest清单文件activity节点下配置1android:launchMode="standard" 横竖屏切换的生命周期默认情况下横竖屏切换activity会被销毁并重新创建 应用启动(横屏):onCreate()->onStart()->onResume()切换竖屏:onPause()->onStop()->onDestory()->onCreate()->onStart()->onResume() 两种解决方案:1.让系统不要理会横竖屏切换(这样就不会重新触发生命周期方法)在配置清单文件activity节点中添加1android:configChanges="orientation|keyboardHidden|screenSize" 2.在配置清单文件activity节点中将屏幕朝向写死1android:screenOrientation=""//portrait竖屏 landscape横屏]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 五]]></title>
<url>%2F2016%2F05%2F16%2Fandroid-note-day05%2F</url>
<content type="text"><![CDATA[多线程下载原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354public void multiThreadDownload(String path){ int threadCount = 3; URL url = new URL(path); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(10000);//设置请求超时时间 conn.setReadTimeout(5000);//设置读取超时时间 conn.setRequestMethod("GET");//设置请求类型 conn.connect(); int responseCode = conn.getResponseCode();//获取请求响应码 if (responseCode == 200) {//请求成功 int length = conn.getContentLength(); int singleFileSize = length / threadCount; //生成一个跟要下载文件大小相同的临时文件 File tempFile = new File("abc.rmvb"); RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rwd"); //创建与服务器上资源同等大小的空文件 tempRaf.setLength(length); tempRaf.close(); for(int i = 0; i < threadCount; i++){ int start = i * singleFileSize; int end = (i + 1) * singleFileSize - 1; if(i == threadCount - 1) end = length - 1; new Thread(){ @Override public void run(){ URL url = new URL(path); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(10000);//设置请求超时时间 conn.setReadTimeout(5000);//设置读取超时时间 conn.setRequestMethod("GET");//设置请求类型 //设置文件读取起始位置及结束位置 conn.setRequestProperty("Range", "bytes=" + start + "-" + end); conn.connect(); if (responseCode == 206) {//请求部分数据,响应码为206 InputStream is = conn.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; //拿到临时文件输出流的引用 File file = new File("abc.rmvb.part"); RandomAccessFile raf = new RandomAccessFile(file, "rwd"); //将文件写入位置移动至start raf.seek(start); while((len = is.read(buffer)) != -1){ raf.write(buffer, 0 ,len); } raf.close(); } } }.start(); } }} 断点续传用单独文件记录下载位置123456789int total;//当前已经下载大小File progressFile = new File(threadId + ".txt");RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");//每次读取数据后,同步把当前下载的总进度接入到进度临时文件中progressRaf.write((total + "").getBytes());progressRaf.close();//继续下载的时候 先判断记录文件是否存在//若存在的话,读取已经下载的大小total,再继续下载start += total; ProgressBar 进度条12345//可以在子线程中刷新UI//设置进度条的最大值pb.setMax(length);//设置当前进度pb.setProgress(total); 第三方框架 xUtils支持多线程、断点续传1234567891011121314151617181920212223242526public void download(String path){ HttpUtils utils = new HttpUtils(); HttpHandler handler = utils.download(path, "", //保存路径 true, //是否支持断点续传,如果服务器不支持range属性,则重新下载 true, //如果从请求返回信息中获取到文件名,下载完成后自动重命名 new RequestCallBack<File>(){ @Override public void onStart(){ } //下载成功后调用 @Override public void onSuccess(ResponseInfo<File> responseInfo){ } @Override public void onFailure(HttpException error, String msg){ } @Override public void onLoading(long total, long current, boolean isUploading){ } });}]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 四]]></title>
<url>%2F2016%2F05%2F15%2Fandroid-note-day04%2F</url>
<content type="text"><![CDATA[handler 消息处理器:用于发送、接收消息主线程中更新UI1234567891011Handler handler=new Handler(){ public void handleMessage(Message msg){ super.handleMessage(msg); switch(msg.what){ case SUCCESS: break; default: break } }}; 子线程向消息池发送消息12345678new Thread(new Runnable(){ public void run(){ Message msg=new Message(); msg.what=0; msg.obj=要操作的变量; handler.sendMessage(msg); }}).start(); get请求123456789101112131415161718192021222324252627282930313233HttpURLConnection conn = null;try { Log.i("getPicture", address); URL url = new URL(address); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(10000);//设置请求超时时间 conn.setReadTimeout(5000);//设置读取超时时间 conn.setRequestMethod("GET");//设置请求类型 conn.connect(); int responseCode = conn.getResponseCode();//获取请求响应码 if (responseCode == 200) {//请求成功 Log.i("get picture", "loading......"); Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream()); if(bitmap!=null) { Message msg = new Message();//向主线程发送一条消息 msg.what = SUCCESS; msg.obj = bitmap; handler.sendMessage(msg); }else{ Message msg = new Message(); msg.what = ERROR; handler.sendMessage(msg); } } else { Log.i("error", "visit wrong"); }} catch (Exception e) { e.printStackTrace();} finally { if (conn != null) conn.disconnect();} 图片缓存12345678910111213141516String urlString = "";URL url = new URL(urlString);HttpURLConnection connection = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(10000);//设置请求超时时间conn.setReadTimeout(5000);//设置读取超时时间conn.setRequestMethod("GET");//设置请求类型conn.connect();InputStream is = connection.getInputStream();byte[] buffer = new byte[1024];File file = new File(getCacheDir(), "");FileOutputStream fos = new FileOutputStream(file);int len = 0;while((len = is.read(buffer)) != -1){ fos.write(buffer, 0, len);}fos.close(); post请求1234567891011121314151617181920212223242526272829303132333435363738394041new Thread(new Runnable(){ public void run(){ //执行网络操作 runOnUiThread(new Runnable(){ public void run(){ //就是在主线程中执行操作 Toast.makeText(getApplicationContext(),"hello",Toast.LENGTH_SHORT).show(); HttpURLConnection conn = null; try { String address = "www.baidu.com"; URL url = new URL(address); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(10000);//设置请求超时时间 conn.setReadTimeout(5000);//设置读取超时时间 conn.setRequestMethod("POST");//设置请求类型 String text = "username=" + name + "&password=" + password; //设置post请求属性 conn.setRequestProperty("Content-Length", text.length() + ""); conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); os.write(text.getBytes()); conn.connect(); int responseCode = conn.getResponseCode();//获取请求响应码 if (responseCode == 200) {//请求成功 InputStream is = conn.getInputStream(); String info = Utils.getTextFromStream(is); } else { Log.i("error", "visit wrong"); } } catch (Exception e) { e.printStackTrace(); } finally { if (conn != null) conn.disconnect(); } } }); }}); 编码问题将汉字转为以%开头的字符串1URLEncoder.encode(editText.getString(), "utf-8"); 采用iso8859-1编码对姓名进行逆转,转成字节数组,再使用utf-8对数据编码1username=new String(username.getBytes("iso8859-1"),"utf-8");]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 三]]></title>
<url>%2F2016%2F05%2F14%2Fandroid-note-day03%2F</url>
<content type="text"><![CDATA[单元测试 新建一个类继承自AndroidTestCase,并实现自己的测试方法 在配置清单文件中添加 123<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="要测试程序的包名" /> 在application节点下添加 1<uses-library android:name="android.test.runner" /> SQLite 数据库的创建1.定义一个类继承自SQLiteOpenHelper 实现其构造函数及onCreate() onUpgrade()方法 super(Context context, String name, CursorFactory factory, int version) 分别为 上下文 数据库文件名 游标工厂(默认为空) 版本号 在onCreate()方法中初始化表 该方法在数据库第一次创建时调用 onUpgrade()方法在数据库版本升级时调用2.定义一个Dao类实现数据库的管理 新建一个上面帮助类变量,实现自己的构造方法,然后实现数据库管理功能123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566void insert(Person person){ SQLiteDatabase db = openHelper.getWritableDatabase(); if(db.isOpen()){ //如果数据库已打开 执行数据的插入 //直接拼接SQL语句不安全,存在SQL注入 String sql = "insert into person(name, age) values(?,?);"; db.execSQL(sql, new Object[]{person.getName(), person.getAge()}); Log.v("INFO", "已插入一条数据 " + person.toString()); db.close();}void delete(int id){ SQLiteDatabase db = openHelper.getWritableDatabase(); if(db.isOpen()){ db.execSQL("delete from person where _id = ?", new Object[]{id}); db.close(); Log.v("INFO", "已删除数据 id = " + id); }}void update(String name,int id){ SQLiteDatabase db = openHelper.getWritableDatabase(); if(db.isOpen()){ db.execSQL("update person set name=? where _id=?", new Object[]{name,id}); db.close(); Log.v("INFO", "已更新数据 id="+id+", name=" + name); }}List<Person> queryAll(){ List<Person> personList = null; SQLiteDatabase db = openHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("select _id, name, age from person", null); Person person = null; if(cursor != null && cursor.getCount() > 0){ personList = new ArrayList<Person>(); while(cursor.moveToNext()){ person = new Person(); person.setId(cursor.getInt(0)); person.setName(cursor.getString(1)); person.setAge(cursor.getInt(2)); personList.add(person); Log.v("INFO", "查询数据:" + person.toString()); } cursor.close();//数据库优化 使用完要记得关闭 db.close(); } return personList;}Person query(int id){ Person person = null; SQLiteDatabase db = openHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("select _id, name, age from person where _id=?", new String[]{String.valueOf(id)}); if(cursor != null && cursor.moveToFirst()){ person = new Person(); person.setId(cursor.getInt(0)); person.setName(cursor.getString(1)); person.setAge(cursor.getInt(2)); Log.v("INFO", "查询数据:" + person.toString()); cursor.close(); db.close(); } return person;} java API 实现数据库增删改查12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182void insert(Person person){ SQLiteDatabase db = openHelper.getWritableDatabase(); ContentValues values = null; if(db.isOpen()){ values = new ContentValues(); values.put("name", person.getName()); values.put("age", person.getAge()); long row=db.insert("person", null, values); Log.v("INFO", "第"+row+"行受影响," + person.toString()); db.close(); }}void delete(int id){ SQLiteDatabase db = openHelper.getWritableDatabase(); if(db.isOpen()){ long count = db.delete("person", "_id=?", new String[]{String.valueOf(id)}); Log.v("INFO", "删除数据:" + count + "行受影响,"); db.close(); }}void update(String name,int id){ SQLiteDatabase db = openHelper.getWritableDatabase(); ContentValues values = null; if(db.isOpen()){ values = new ContentValues(); values.put("name", name); long count = db.update("person", values, "_id=?", new String[]{String.valueOf(id)}); Log.v("INFO", "更新数据:" + count + "行受影响,"); db.close(); }}List<Person> queryAll(){ List<Person> personList = null; Person person = null; SQLiteDatabase db = openHelper.getReadableDatabase(); if(db.isOpen()){ personList = new ArrayList<Person>(); String[] columns = new String[]{"_id", "name", "age"};//需要查询的列 String selection = null;//选择条件为空 查询所有 String[] selectionArgs = null;//选择条件参数 String groupBy = "name";//分组查询 group by name String having = null;//过滤语句 String orderBy = null;//排序 Cursor cursor = db.query("person", columns, selection, selectionArgs, groupBy, having, orderBy); if(cursor != null && cursor.getCount() > 0){ while(cursor.moveToNext()){ person = new Person(); person.setName(cursor.getString(1)); person.setAge(cursor.getInt(2)); personList.add(person); } } cursor.close(); db.close(); } return personList;}Person queryItem(int id){ Person person = null; SQLiteDatabase db = openHelper.getReadableDatabase(); if(db.isOpen()){ String[] columns = new String[]{"_id", "name", "age"};//需要查询的列 String selection = "_id=?";//选择条件为空 查询所有 String[] selectionArgs = new String[]{String.valueOf(id)};//选择条件参数 String groupBy = "name";//分组查询 group by name String having = null;//过滤语句 String orderBy = null;//排序 Cursor cursor = db.query("person", columns, selection, selectionArgs, groupBy, having, orderBy); if(cursor != null && cursor.moveToFirst()){ person = new Person(); person.setName(cursor.getString(1)); person.setAge(cursor.getInt(2)); } cursor.close(); db.close(); } return person;} SQLite3的使用123456789101112adb shellls //列出当前目录下文件与文件夹cd /data/data/包名cd databasessqlite3 数据库文件名select * from person;delete from person where _id=3;.table //列出数据库中表名.mode column //更改输出模式.exit //退出SQLite3 SQLite事务操作要知道开启事务操作,效率要比不开启提升了10倍左右123456789101112131415161718192021222324252627void testTransaction(){ SQLiteDatabase db = openHelper.getWritableDatabase(); if(db.isOpen()){ try{ //开启事务 db.beginTransaction(); String sql = "update person set balance=balance-1000 where name='ai';"; db.execSQL(sql); //double i = 10/0; String sql2 = "update person set balance=balance+1000 where name='zhangsan';"; db.execSQL(sql2); //标记事务成功 db.setTransactionSuccessful(); }catch(Exception e){ e.printStackTrace(); }finally{ //关闭事务 db.endTransaction(); db.close(); } }} ListView自定义适配器一getView()方法返回的为TextView123456789101112131415161718192021222324252627282930313233343536373839404142class MyAdapter extends BaseAdapter{ @Override public int getCount() { return personList.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } /** * 返回的是ListView中某一行view对象 * position 当前返回的view的索引位置 * convertView 缓存对象 * parent ListView对象 */ @Override public View getView(int position, View convertView, ViewGroup parent) { TextView tv = null; //向下滚动 上面内容消失后会被缓存 if(convertView != null){//复用缓存对象优化ListView tv = (TextView) convertView; Log.v("INFO", "复用:" + position); }else{ tv = new TextView(getApplicationContext()); Log.v("INFO", "新建:" + position); } tv.setTextSize(30); tv.setTextColor(Color.BLACK); Person person = personList.get(position); tv.setText(person.toString()); return tv; }} 自定义适配器二getView()方法返回的为Layout布局1234567891011121314151617181920212223242526272829303132333435363738394041424344class MyAdapter_2 extends BaseAdapter{ @Override public int getCount() { return personList.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } /** * 返回的是ListView中某一行view对象 * position 当前返回的view的索引位置 * convertView 缓存对象 * parent ListView对象 */ @Override public View getView(int position, View convertView, ViewGroup parent) { View view = null; LayoutInflater inflater = null; if(convertView != null){ view = convertView; Log.v("INFO", "复用:" + position); }else{ //将布局文件转换为一个对象 inflater = MainActivity.this.getLayoutInflater(); view = inflater.inflate(R.layout.adapter2_itemlist, null); Log.v("INFO", "新建:" + position); } TextView name_textview = (TextView)view.findViewById(R.id.name_view); TextView age_textview = (TextView)view.findViewById(R.id.age_view); Person person = personList.get(position); name_textview.setText(person.getName()); age_textview.setText(person.getAge() + ""); return view; }} ListView 绑定ArrayAdapter适配器12String[] persons = new String[]{"A","B","C","D","E","F","G","H"};arrayAdapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, persons); ListView 绑定SimpleAdapter适配器12345678910data = new ArrayList<Map<String,Object>>();for(Person person : personList){ Map<String,Object> map = new HashMap<String, Object>(); map.put("name", person.getName()); map.put("age", person.getAge()); data.add(map);}String[] from = new String[]{"name", "age"};int[] to = new int[]{R.id.title_textview, R.id.subtitle_textview};adapter = new SimpleAdapter(getApplicationContext(), data, R.layout.item_list, from, to); ListView动态更新1adapter.notifyDataSetChanged(); 对话框1234567891011121314151617AlertDialog.Builder builder = new Builder(this);builder.setIcon(android.R.drawable.alert_dark_frame) .setTitle("Title") .setMessage("message") .setPositiveButton("确定", new OnClickListener(){ @Override public void onClick(DialogInterface dialog, int which){ Toast.makeText(MainActivity.this, "确定", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("取消", new OnClickListener(){ @Override public void onClick(DialogInterface dialog, int which){ Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show(); } });builder.create().show(); 单选对话框1234567891011final String[] items = new String[]{"男", "女"};AlertDialog.Builder builder = new Builder(this);builder.setTitle("请选择性别") .setSingleChoiceItems(items, -1, new OnClickListener(){ @Override public void onClick(DialogInterface dialog, int which){ Toast.makeText(MainActivity.this, "您选择的是" + items[which], Toast.LENGTH_SHORT).show(); dialog.dismiss(); } });builder.create().show(); 多选会话框123456789101112131415161718final String[] items = new String[]{"蓝莓", "西瓜", "香蕉", "苹果"};boolean[] checkedItems = new boolean[]{true, true, true, false};AlertDialog.Builder builder = new Builder(this);builder.setTitle("请选择你喜欢的水果") .setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener(){ @Override public void onClick(DialogInterface dialog, int which, boolean isChecked){ checkedItems[which] = isChecked; } }) .setPositiveButton("确定", new OnClickListener(){ @Override public void onClick(DialogInterface dialog, int which){ Toast.makeText(MainActivity.this, "你选择的是:", Toast.LENGTH_SHORT).show(); dialog.dismiss(); } });builder.create().show();]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 二]]></title>
<url>%2F2016%2F05%2F13%2Fandroid-note-day02%2F</url>
<content type="text"><![CDATA[使用路径 api 读写文件 getFilesDir()得到的file对象的路径是data/data/com.itheima.rwinrom2/files存放在这个路径下的文件,只要你不删,它就一直在 getCacheDir()得到的file对象的路径是data/data/com.itheima.rwinrom2/cache存放在这个路径下的文件,当内存不足时,有可能被删除 系统管理应用界面的清除缓存,会清除cache文件夹下的东西,清除数据,会清除整个包名目录下的东西 获取SD卡路径 1Environment.getExternalStorageDirectory(); 检测 SD 卡状态1234567if(Environment.getExternalStorageState().equals(Environment.MEDIA_UNKNOWN)){ //不能识别SD卡}//MEDIA_UNMOUNTED:SD卡存在但是没有挂载//MEDIA_REMOVED:没有SD卡//MEDIA_CHECKING:SD卡正在准备//MEDIA_MOUNTED:SD卡已经挂载,可以使用 获取 SD 卡剩余可用空间1234567891011121314//获取SD卡内存状态File pt = Environment.getExternalStorageDirectory();//获取手机内部存储状态File ph = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath());long blockSize = stat.getBlockSize();long totalBlocks = stat.getBlockCount();long availableBlocks = stat.getAvailableBlocks(); String totalMemory = Formatter.formatFileSize(this, totalBlocks * blockSize);String availableMemory = Formatter.formatFileSize(this, availableBlocks * blockSize);//"总空间为:" + totalMemory + " 可用空间为:" + availableMemory; 获取当前系统版本的等级1if(Build.VERSION.SDK_INT >= 18){} Linux 文件的访问权限 在Android中,每一个应用是一个独立的用户 drwxrwxrwx 第1位:d表示文件夹,-表示文件 第2-4位:rwx,表示这个文件的拥有者用户(owner)对该文件的权限 r:读 w:写 x:执行 第5-7位:rwx,表示跟文件拥有者用户同组的用户(grouper)对该文件的权限 第8-10位:rwx,表示其他用户组的用户(other)对该文件的权限 openFileOutput 的四种模式123//创建 info.txt 文件,并指定访问权限FileOutputStream fos = openFileOutput("info.txt", MODE_PRIVATE);//文件路径默认为 /data/data/包名/files MODE_PRIVATE: -rw-rw—- MODE_APPEND: -rw-rw—- MODE_WORLD_WRITEABLE: -rw-rw–w- MODE_WORLD_READABLE: -rw-rw-r– 使用 SharedPreference 保存数据12345678//保存数据SharedPreference sp = getSharedPreference("info", MODE_PRIVATE);SharedPreference.Editor editor = sp.edit();editor.put("key", "value");editor.commit();//读取数据 如果没有取到数据,则返回"default_value"String value = sp.getString("key", "default_value"); 序列化生成 xml 文件12345678910111213141516171819202122232425262728293031323334353637383940414243File file = null;FileOutputStream fos = null;XmlSerializer xmlSerializer = Xml.newSerializer();try { file = new File(getApplicationContext().getFilesDir(),"persons.xml"); fos = new FileOutputStream(file); xmlSerializer.setOutput(fos, "utf-8"); xmlSerializer.startDocument("utf-8", true); xmlSerializer.startTag(null, "persons"); for (Person person : personList) { xmlSerializer.startTag(null, "person"); xmlSerializer.attribute(null, "id", String.valueOf(person.getId())); xmlSerializer.startTag(null, "name"); xmlSerializer.text(person.getName()); xmlSerializer.endTag(null, "name"); xmlSerializer.startTag(null, "age"); xmlSerializer.text(String.valueOf(person.getAge())); xmlSerializer.endTag(null, "age"); xmlSerializer.endTag(null, "person"); } xmlSerializer.endTag(null, "persons"); xmlSerializer.endDocument(); xmlSerializer.flush(); fos.close(); return true;} catch (Exception e) { e.printStackTrace();} finally { try { if (fos != null) fos.close(); } catch (Exception e) { e.printStackTrace(); }} pull 解析 xml 文件123456789101112131415161718192021222324252627282930313233343536373839404142434445File file;FileInputStream fis=null;XmlPullParser xmlPullParser;Person person=null;try{ file=new File(getApplicationContext().getFilesDir(),"persons.xml"); fis=new FileInputStream(file); xmlPullParser=Xml.newPullParser(); xmlPullParser.setInput(fis, "utf-8"); int eventType=xmlPullParser.getEventType(); while(eventType!=XmlPullParser.END_DOCUMENT){ String tagName=xmlPullParser.getName(); switch(eventType){ case XmlPullParser.START_TAG: if(tagName.equals("person")){ person=new Person(); person.setId(Integer.parseInt(xmlPullParser.getAttributeValue(null, "id"))); }else if(tagName.equals("name")){ person.setName(xmlPullParser.nextText()); }else if(tagName.equals("age")){ person.setAge(Integer.parseInt(xmlPullParser.nextText())); } break; case XmlPullParser.END_TAG: if(tagName.equals("person")){ personList.add(person); } break; default:break; } eventType=xmlPullParser.next(); }}catch(Exception e){ e.printStackTrace();}finally{ try{ if(fis!=null) fis.close(); }catch(Exception e){ e.printStackTrace(); }}]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 系列笔记 一]]></title>
<url>%2F2016%2F05%2F13%2Fandroid-note-day01%2F</url>
<content type="text"><![CDATA[发送短信自定义短信发送12345678String content = "短信内容";SMSManager smsManager = SmsManager.getDefault();//切割短信(若短信超过一条短信规定的字数)ArrayList<String> smsList = smsManager.divideMessage(content);//发送切割后的短信for(String msg : smsList){ smsManager.sendTextMessage("10086", null, msg, null, null);} 调用系统短信应用发送短信1234567891011Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("sendto:10086"));intent.putExtra("sms_body", "hello, everyone");startActivity(intent);/*说明 sms.sendTextMessage(destinationAddress, scAddress, text, sentIntent, deliveryIntent); destinationAddress:接收方的手机号码 scAddress:短信中心号码 text:信息内容 sentIntent:发送是否成功的广播回执, DeliveryIntent:对方接收是否成功的广播回执。*/ 打电话拨号器与打电话是两个不同的应用1234567Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + number));//或者Intent intent=new Intent();intent.setAction(Intent.ACTION_CALL);intent.setData(Uri.parse("tel:" + number));startActivity(intent);]]></content>
<categories>
<category>android</category>
<category>notes</category>
</categories>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[博文的开始]]></title>
<url>%2F2016%2F04%2F05%2Fthe-start-of-my-blog%2F</url>
<content type="text"><![CDATA[前言前段时间不知道什么原因,博客挂掉了,刚好也想换个主题,翻了下 GitPages 上的主题,瞬间被 NexT 的 Mist 主题给吸引了,本来就比较喜欢极简的风格,看到 Mist 都有种相见恨晚的感觉,然后就开始了我的折腾之旅。 搭建环境Windows 安装 GitBash 与 Node.js,博客主题是基于 hexo 的,因此我们还要安装 hexo 在你喜欢的地方新建一个文件夹,命名 hexo 在 hexo 文件夹上右键,选择 git bash,在弹出来的窗口中输入下面指令完成 hexo 的安装1$ npm install -g hexo 安装完成之后,继续依次输入如下命令,完成 hexo 的部署12$ hexo init $ npm install Mac HomeBrew 安装打开终端,输入下面命令 1$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" HomeBrew 是什么Homebrew 简称 brew,是 Mac OSX 上的软件包管理工具,能在 Mac 中方便的安装软件或者卸载软件,可以说 Homebrew 就是 mac 下的 apt-get、yum 神器。 安装 Node.js 1$ brew install node 安装 部署 hexo新建一个文件夹,命名为 hexo,在终端中切换路径到该文件夹中,输入下面命令: 123$ npm install -g hexo$ hexo init$ npm install 安装 git 1$ sudo brew install git 更换主题 NexT 下载 NexT 主题到本地将 终端/命令行 切换到上面新建的文件夹 hexo 所在的路径,输入下面的指令: 1$ git clone https://github.com/iissnan/hexo-theme-next themes/next 编辑站点配置文件切换 NexT 主题打开 站点目录中的 _config.yml,将 theme 后的字段改为 next。 编辑主题中配置文件启用 NexT 中 Mist 主题打开 主题 NexT 中的 _config.yml,找到 Schemes,将 Mist 前的 # 去掉来启用 Mist 主题。 本地预览 将命令行切换到 hexo 站点所在的路径输入以下命令: 123$ hexo clean //清除上次站点生成的文件$ hexo g //生成$ hexo s //启动本地服务,进行文章预览调试 在浏览器中输入 http://localhost:4000, 预览博客。]]></content>
<categories>
<category>随笔</category>
</categories>
<tags>
<tag>hexo</tag>
<tag>blog</tag>
</tags>
</entry>
</search>