1. 前言
1.1. 什么是3G、4G
Ÿ 第三代移动通信技术(3rd - Generation),速率一般在几百Kbps,较之前的2G和2.5G在数据传输速度上有很大提升。
Ÿ 第四代移动通信技术(4th - Generation),速度可达到100Mbps以上,几乎可以满足人们的所有传输数据的需求。
Ÿ 目前主流的3G技术标准有三种:
WCDMA:全球80%以上的3G网络都是采用此种制式。中国联通运营。186
CDMA2000:目前日韩及北美使用较多。中国电信运营。 189
TD-SCDMA:中国自主知识产权的3G通信技术。中国移动运营。 188
Ÿ 目前主流的4G技术为LTE,但还没有被广泛应用:
GSM à GPRS à EDGE à WCDMA à HSDPA à HSDPA+ à LTE
1.2. 什么是Android
Ÿ Android本义指“机器人”,Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称,官方中文名:安卓 。
Ÿ Android系统早期由原名为"Android"的公司开发,谷歌在2005年收购"Android.Inc"后,继续对Android系统开发运营。
Ÿ 底层Linux内核只提供基本功能,其他的应用软件则由各公司自行开发,大部分程序以Java语言编写。
Ÿ 由于Android系统的开源特性,很多制造商都在生产Android系统的设备,如:摩托罗拉、HTC、三星、索爱、LG、小米、华为、魅族等。
Ÿ Android系统除了运行在智能手机上之外,还可以用做平板电脑、电视、汽车等很多设备上。
Ÿ Android系统架构图:
1.3. Android的版本升级
Ÿ 1.5 Cupcake(纸杯蛋糕)
拍摄/播放影片,并支持上传到Youtube
支持立体声蓝牙耳机,同时改善自动配对性能
最新的采用WebKit技术的浏览器,支持复制/贴上和页面中搜索
GPS性能大大提高
提供屏幕虚拟键盘
主屏幕增加音乐播放器和相框widgets
应用程序自动随着手机旋转
短信、Gmail、日暦,浏览器的用户接口大幅改进,如Gmail可以批量删除邮件
相机启动速度加快,拍摄图片可以直接上传到Picasa
来电照片显示
Ÿ 1.6 Donut(甜甜圈)
重新设计的Android Market
手势支持
支持CDMA网络
文字转语音系统(Text-to-Speech)
快速搜索框
全新的拍照接口
查看应用程序耗电
支持虚拟私人网络(VPN)
支持更多的屏幕分辨率。
支持OpenCore2媒体引擎
新增面向视觉或听觉困难人群的易用性插件
Ÿ 2.1 Éclair(闪电泡芙)
优化硬件速度
"Car Home"程序
支持更多的屏幕分辨率
改良的用户界面
新的浏览器的用户接口和支持HTML5
新的联系人名单
更好的白色/黑色背景比率
改进Google Maps 3.1.2
支持内置相机闪光灯
支持数码变焦
改进的虚拟键盘
支持蓝牙2.1
支持动态桌面的设计
Ÿ 2.2 Froyo(冻酸奶)
支持将软件安装至扩展内存
集成Adobe Flash 10.1支持
加强软件即时编译的速度
新增软件启动"快速"至电话和浏览器
USB分享器和WiFi热点功能
支持在浏览器上传档案
更新Market中的批量和自动更新
增加对Microsoft Exchange的支持(安全政策, auto-discovery, GAL look-up)
集成Chrome的V8 JavaScript引擎到浏览器
加强快速搜索小工具
更多软件能透过Market更新,类似2.0/2.1中的Map更新
速度和性能优化
Ÿ 2.3 Gingerbread(姜饼)
修补UI
支持更大的屏幕尺寸和分辨率(WXGA及更高)
系统级复制粘贴
重新设计的多点触摸屏幕键盘
本地支持多个镜头(用于视频通话等)和更多传感器(陀螺仪、气压计等)
电话簿集成Internet Call功能
强化电源、应用程序管理功能
新增下载管理员
优化游戏开发支持
多媒体音效强化
开放了屏幕截图功能
对黑色及白色的还原更加真实
Ÿ 3.x Honeycomb(蜂巢)
仅供平板电脑使用
Google eBooks上提供数百万本书
支持平板电脑大萤幕、高分辨率
新版Gmail
Google Talk视讯功能
3D加速处理
网页版Market(Web store)详细分类显示,依个人Android分别设定安装应用程序
新的短消息通知功能
专为平板电脑设计的用户界面(重新设计的通知列与系统列)
加强多任务处理的接口
重新设计适用大屏幕的键盘及复制粘贴功能
多个标签的浏览器以及私密浏览模式
快速切换各种功能的相机
增强的图库与快速滚动的联络人接口
更有效率的Email接口
支持多核心处理器
3.2优化7吋平板显示
Ÿ 4.0 Ice Cream Sandwich(奶油三明治)
虚拟按键,增大屏幕面积同时控制手机整体大小
桌面插件Widgets列表呈现在标签页中,与程序列表类似并且共存
文件夹更容易创建和管理,与iOS类似
可定制的桌面系统
可视语音邮件
日历支持缩放操作
Gmail离线搜索,两行预览,以及底部新快捷栏
音量下键+电源键组合截图
改进虚拟键盘纠错
从锁屏界面直接访问应用程序
优化复制粘贴
新版浏览器
新的Roboto字体
流量控制系统
相机应用
人脸识别,刷脸解锁
内置照片编辑器
多任务列表
新的图库布局和组织方式
联系人应用整合社交网络信息
Android Beam
http://digi.tech.qq.com/a/20111019/001579.htm
1.4. 主流智能手机操作系统
2. 搭建开发环境
2.1. 所需资源
Ÿ JDK,Java开发环境。下载地址:http://www.oracle.com
Ÿ Eclipse,IBM公司开发的一款开源IDE。http://www.eclipse.org
Ÿ Android SDK,Android 开发工具,包含开发Android程序所需类库、源码、文档、案例等资源。http://www.android.com
Ÿ ADT插件,ADT 是 Eclipse 平台下用来开发 Android 应用程序的插件。http://www.android.com
2.2. Eclipse安装ADT插件
2.3. 配置SDK路径
2.4. 启动虚拟机
- 点击机器人图标
- 弹出虚拟机管理器
- 修改虚拟机默认目录
指定环境变量android_sdk_home,通常指定为SDK所在目录,可以随意指定。
重启Eclipse之后将会以这个目录作为存放虚拟机文件的位置。
- 配置虚拟机
Ÿ Name:虚拟机的名字,可随意定义
Ÿ Target:虚拟机版本
Ÿ SD Card - Size:虚拟机的SDCard大小,会在本地硬盘创建指定大小的文件用来存储数据,模拟真实手机的SDCard
如果使用原有文件,可以选择File并点击Browse指定文件
Ÿ Snapshot: 保存快照,可以提高虚拟机启动速度
Ÿ Skin - Built-in:选择分辨率
HVGA:480x320
QVGA:320x240
WQVGA400:400x240
WQVGA432:432x240
WVGA800:800x480
WVGA854:854x480
也可以选择Resolution自行指定
- 成功进入Android操作系统界面
Ÿ 如果虚拟机显示无信号,需要对电脑的网络连接进行配置
无论电脑是否联网,IP地址、子网掩码、默认网关、首选DNS服务器都需要配置
Ÿ 电脑如果未连接局域网或互联网,可以见默认网关和首选DNS服务器配置成本机IP,例如:
Ÿ 电脑如果已连接局域网,需要将网关和首选DNS服务器进行设置,例如:
Ÿ 如果电脑已连接互联网,正常情况虚拟机不会显示无信号,重启虚拟机
2.5. 命令行操作
为了能在任意目录使用一下命令,需要将SDK目录下的platform-tools文件夹路径和tools文件夹路径配置到path环境变量中
Ÿ 列出可以使用的android版本
android list targets
Ÿ 列出可以使用的虚拟机
android list avd
Ÿ 创建虚拟机
android create avd –n <虚拟机名> -t <Target版本ID> -c <SD卡大小> -s <屏幕尺寸>
Ÿ 启动虚拟机
emulator –avd <虚拟机名>
Ÿ 显示已连接的设备
adb devices
Ÿ 导入文件到手机
adb push <Windows源文件路径> <手机目标路径>
Ÿ 从手机导出文件
adb pull <手机源文件路径> <Windows目录路径>
Ÿ 安装程序
adb install <apk文件路径>
Ÿ 卸载程序
adb uninstall <包名>
Ÿ 重启adb
adb kill-server
abd start-server
3. 开发一个Android程序
3.1. 创建Android程序
- 创建Android Project
Ÿ Project name:项目名
Ÿ Build Target:Android版本
Ÿ Application name:程序名,显示在程序列表中,以及程序启动后的标题栏
Ÿ Package name:包名,程序的唯一标识
Ÿ Create Activity:选择程序启动时是否创建一个窗体,设置主窗体名字
Ÿ Min SDK Version:设置运行该程序所需的最低版本
3.2. 安装、卸载程序
Ÿ Eclipse安装
右键点击工程 – Run as – Android Application
Ÿ 虚拟机卸载
设置 – 应用程序 – 管理应用程序 – 选中要卸载的程序 – 卸载
3.3. 了解项目目录结构
Ÿ src:源代码
Ÿ gen:系统自动生成的文件
R.java 中记录了项目中各种资源ID
Ÿ res:系统资源,所有文件都会在R文件生成资源ID
drawable:图片
layout:界面布局
values:数据
anim:定义动画的XML
raw:原生文件
Ÿ assets:资源路径,不会在R文件注册
Ÿ project.properties:供Eclipse使用,读取该项目使用Android版本号。早期版本名为:default.properties
Ÿ AndroidManifest.xml:清单文件,在软件安装的时候被读取
Android中的四大组件(Activity、ContentProvider、BroadcastReceiver、Service)都需要在该文件中注册
程序所需的权限也需要在此文件中声明,例如:电话、短信、互联网、访问SD卡
Ÿ bin:二进制文件,包括class、资源文件、dex、apk等
Ÿ proguard.cfg:用来混淆代码的配置文件,防止别人反编译
3.4. 程序启动过程
Ÿ Eclipse将.java源文件编译成.class
Ÿ 使用dx工具将所有.class文件转换为.dex文件
Ÿ 再将.dex文件和所有资源打包成.apk文件
Ÿ 将.apk文件安装到虚拟机完成程序安装
Ÿ 启动程序 – 开启进程 – 开启主线程
Ÿ 创建Activity对象 – 执行OnCreate()方法
Ÿ 按照main.xml文件初始化界面
4. 演示案例
4.1. 电话拨号
Ÿ 搭建界面需要组件:TextView、EditText、Button
Ÿ 当点击Button时获取EditText中文本
Ÿ 使用Intent向系统内置的电话拨号器发送意图拨打电话
Ÿ 注册拨打电话权限
4.2. 查看程序错误信息
Ÿ Android程序中如果出错,错误不会显示在Console中,而是显示在LogCat界面下。可以从window – show view中打开
Ÿ 日志信息分为5个级别:verbose > debug > info > warn > error 高级的包含低级的
Ÿ 可以创建过滤器对日志进行过滤显示,点击绿色加号,可以按照tag、pid、level进行筛选
4.3. 将程序安装到真实手机
Ÿ 在电脑上安装手机驱动
有些手机自带驱动,有些没有,可以从官网下载。或者可以使用“豌豆荚”软件自动安装。
Ÿ 在手机设置中打开USB调试,将手机用USB数据线连接到电脑
我的手机是:三星 i9100
双核1228MHz、1GB RAM 、4GB ROM、480×800像素、Android 2.3、4.3英寸
Ÿ 检查Eclipse的设备管理器中是否显示出新设备
如果未能显示出设备,检查驱动安装是否正常,USB调试是否打开
Ÿ Eclipse安装程序
Eclipse上右键点击工程 – Run as – Android Application – 自动安装运行
Ÿ 手动打包安装
右键点击工程 – Export – Export Android Application – 选择或创建密钥对程序签名并打包生成apk文件
将apk文件放到手机的SD卡中,通过手机文件浏览器执行安装
4.4. 短信发送
Ÿ 搭建界面需要组件:TextView、EditText、Button
Ÿ 给Button添加监听器,当被点击的时候获取号码,获取内容
Ÿ 使用SmsManager发送短信
Ÿ 需要注册短信发送权限
4.5. 布局
- RelativeLayout(相对布局)
Ÿ android-sdk-windows/docs/guide/topics/ui/layout-objects.html#relativelayout
- TableLayout(表格布局)
android-sdk-windows/docs/guide/topics/ui/layout-objects.html#tablelayout
- FrameLayout(帧布局)
android-sdk-windows/docs/guide/topics/ui/layout-objects.html#framelayout
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
4.6. Junit
- 项目中添加测试类
Ÿ 在AndroidManifest.xml清单文件中添加配置
<instrumentation android:targetPackage="cn.itcast.junit" android:name="android.test.InstrumentationTestRunner" />
<uses-library android:name="android.test.runner" />
Ÿ 定义一个类继承AndroidTestCase,定义测试方法
Ÿ 在Outline视图下右键点击测试方法 – Run as – Android Junit Test
- 创建测试项目
Ÿ 创建Android Test Project
Ÿ 输入项目名,选择一个已存在的工程,Eclipse可以自动配置Junit环境
4.7. 日志信息
Ÿ 在LogCat视图中我们可以看到程序的日志信息,也可以在程序中输出信息到LogCat中
Ÿ 程序中我们可以使用Log类来输出信息
Ÿ System.out和System.err输出的信息也会显示在LogCat中,注意System.out输出信息是Info级别,System.err是Warn级别
5. 文件操作(File、XML、SharedPreferences)
5.1. 读写文件
- 写入文件到SD卡
Ÿ 需要在清单文件中注册权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Ÿ 2.1版本以下的SDCard位置和2.2之后版本不同
可以通过Environment.getExternalStorageDirectory()获取当前SDCard位置,兼容所有版本
Ÿ 获取SDCard状态
通过Environment.getExternalStorageState()方法获取SDCard当前状态
常量 Environment.MEDIA_MOUNTED 为已安装
- 写入文件
Ÿ 通过Context.openFileOutput(String name, int mode)可以获取一个文件输入流
name为文件名,mode为文件模式,有4种模式
输出流指向路径为:/data/data/包名/files/
Ÿ 文件模式在Context中有定义常量
MODE_PRIVATE 私有
MODE_WORLD_READABLE 其他程序可读(不可写)
MODE_WORLD_WRITEABLE 其他程序可写(不可读)
模式可以组合使用,例如:MODE_WORLD_READABLE + MODE_WORLD_WRITEABLE
MODE_APPEND 追加
- 读取文件
Ÿ 通过Context.openFileInput(String name)可以获取一个文件输入流
该输入流可以读取 /data/data/包名/files/ 路径下的文件
Ÿ 获取当前程序Files文件路径
ContextWrapper.getFilesDir()
5.2. XML
- Pull简介
Ÿ 常见的XML解析方式有三种,DOM、SAX、Pull,Android系统中推荐使用Pull
Ÿ Pull解析器是一个开源的Java项目,Android系统内部解析XML文件均为此种方式,也可用于JavaEE项目
Ÿ Android SDK中已经集成了Pull解析器,无需添加任何jar文件
Ÿ Pull解析器运行方式与SAX类似,提供各种事件的判断
Ÿ 官方网站:http://xmlpull.org/
- 使用Pull解析器解析XML文件
Ÿ Xml.newPullParser() 获得解析器
Ÿ parser.setInput(in, "UTF-8") 设置输入流以及编码
Ÿ parser.next() 获取下一个解析事件,得到一个事件代码
Ÿ XmlPullParser中定义了常量来标识各种解析事件
START_DOCUMENT、END_DOCUMENT 、START_TAG 、END_TAG 、TEXT
- 使用XmlSerializer写出XML
Ÿ 使用以下方法生成XML,和XML文档顺序类似
startDocument
startTag
attribute
text
endTag
endDocument
5.3. 偏好设定(SharedPreferences)
Ÿ 在程序中保存一些配置参数的时候我们经常使用SharedPreferences
Context.getSharedPreferences(String name,int mode)
该方法可以在/data/data/<package>/shared_pref/目录下创建一个以name命名的xml文件,mode文件为模式
Ÿ 存储偏好
调用edit()方法可以获取一个Editor对象,对数据进行存储,存储之后需要调用commit()保存到文件
Ÿ 读取偏好
获得SharedPreferences之后调用getString()、getInt()等方法获取其中设置的值
Ÿ 在Activity中获取SharedPreferences
在Activity中可以调用getPreferences(int mode)方法获得一个SharedPreferences,文件名和Activity名一致
6. 数据库(SQLite)
6.1. SQLite特点
Ÿ Android平台中嵌入了一个关系型数据库SQLite,和其他数据库不同的是SQLite存储数据时不区分类型
例如一个字段声明为Integer类型,我们也可以将一个字符串存入,一个字段声明为布尔型,我们也可以存入浮点数。
除非是主键被定义为Integer,这时只能存储64位整数
Ÿ 创建数据库的表时可以不指定数据类型,例如:
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20))
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name)
Ÿ SQLite支持大部分标准SQL语句,增删改查语句都是通用的,分页查询语句和MySQL相同
SELECT * FROM person LIMIT 20 OFFSET 10
SELECT * FROM person LIMIT 10,20
6.2. 创建数据库
Ÿ 定义类继承SQLiteOpenHelper
Ÿ 声明构造函数,4个参数
Ÿ 重写onCreate()方法
Ÿ 重写upGrade()方法
Ÿ 注意:SQLite数据库中列一旦创建不能修改,如果一定要修改,需要重新创建表,拷贝数据
6.3. CRUD操作
Ÿ 和JDBC访问数据库不同,操作SQLite数据库无需加载驱动,不用获取连接,直接可以使用
获取SQLiteDatabase对象之后通过该对象直接可以执行SQL语句
SQLiteDatabase.execSQL()
SQLiteDatabase.rawQuery()
Ÿ getReadableDatabase()和getWritableDatabase()的区别
查看源代码后我们发现getReadableDatabase()在通常情况下返回的就是getWritableDatabase()拿到的数据库
只有在抛出异常的时候才会以只读方式打开
Ÿ 数据库对象缓存
getWritableDatabase()方法最后会使用一个成员变量记住这个数据库对象,下次打开时判断是否重用
Ÿ SQLiteDatabase封装了insert()、delete()、update()、query()四个方法也可以对数据库进行操作
这些方法封装了部分SQL语句,通过参数进行拼接
6.4. 事务管理
Ÿ 在使用SQLite数据库时可以用SQLiteDatabase类中定义的相关方法控制事务
beginTransaction() 开启事务
setTransactionSuccessful() 设置事务成功标记
endTransaction() 结束事务
Ÿ endTransaction()需要放在finally中执行,否则事务只有到超时的时候才自动结束,会降低数据库并发效率
7. 内容提供者(ContentProvider)
7.1. 什么是内容提供者
Ÿ 内容提供者是Android中的四大组件之一,可以将应用中的数据对外进行共享
Ÿ 内容提供者将数据的访问方式统一,不必针对不同数据类型采取不同的访问策略
Ÿ 内容提供者将数据封装,只暴露出我们希望提供给其他程序的数据
Ÿ 内容提供者中数据更改可被监听
7.2. 创建内容提供者
Ÿ 定义类继承ContentProvider,根据需要重写内部方法
Ÿ 在清单文件的<application>节点下进行配置,<provider>标签中需要指定name和authorities属性
name为类名,包名从程序Package开始,以“.”开始
authorities:是访问Provider时的路径,要唯一
Ÿ URI代表要操作的数据,由scheme、authorites、path三部分组成
content://cn.itcast.sqlite.provider/person
scheme:固定为content,代表访问内容提供者
authorites:<provider>节点中的authorites属性
path:程序定义的路径,可根据业务逻辑定义
7.3. 完成CRUD方法
Ÿ 当程序调用CRUD方法时会传入Uri
Ÿ 我们通过Uri判断调用者要操作的数据
可以使用工具类UriMatcher来判断Uri
addURI方法可以添加Uri
match方法可以匹配一个Uri判断其类型
Ÿ 根据业务逻辑操作数据
7.4. 访问内容提供者
Ÿ 通过Context获得ContentResolver对象
Ÿ 调用ContentResolver对象的方法即可访问内容提供者
7.5. 完成getType方法
Ÿ 如果返回数据是单条数据:vnd.android.cursor.item
Ÿ 如果返回数据是多条数据:vnd.android.cursor.dir
7.6. 监听内容提供者数据变化
Ÿ 在内容提供者中可以通知其他程序数据发生变化
通过Context的getContentResolver()方法获取ContentResolver
调用其notifyChange()方法发送数据修改通知
Ÿ 在其他程序中可以通过ContentObserver监听数据变化
通过Context的getContentResolver()方法获取ContentResolver
调用其registerContentObserver()方法指定对某个Uri注册ContentObserver
自定义ContentObserver,重写onChange()方法获取数据
7.7. GIT获取源代码
- 资源地址
Ÿ Git
http://code.google.com/p/msysgit/
Ÿ 源码
注意:
GIT1.7.7安装后不能卸载,可以用其他版本覆盖后再卸载。
使用GIT时不要使用中文目录,否则GIT GUI会报错无法启动。删除C盘中.gitconfig文件可以解决。
7.8. 监听短信
Ÿ Android系统提供了Provider对短信进行查询,当发出短信时也会发送更改通知
Ÿ 短信数据库在 com.android.providers.telephony
Ÿ 定义一个Observer监听"content://sms"
Ÿ 在onChange()方法中查询"content://sms"
Ÿ 需要权限android.permission.READ_SMS
7.9. 操作联系人
- 获取所有联系人
Ÿ Android系统中的联系人也是通过ContentProvider来对外提供数据的
Ÿ 数据库路径为:/data/data/com.android.providers.contacts/database/contacts2.db
Ÿ 我们需要关注的有3张表
raw_contacts:其中保存了联系人id
data:和raw_contacts是多对一的关系,保存了联系人的各项数据
mimetypes:为数据类型
Ÿ Provider的authorites为com.android.contacts
Ÿ 查询raw_contacts表的路径为:contacts
Ÿ 查询data表的路径为:contacts/#/data
这个路径为连接查询,直接查询“mimetype”字段就可以根据“mimetype_id”查询到mimetypes表中的数据
Ÿ 先查询raw_contacts得到每个联系人的id,在使用id从data表中查询对应数据,根据mimetype分类数据
- 通过电话号码获取联系人
Ÿ 系统内部提供了根据电话号码获取data表数据的功能,路径为:data/phones/filter/*
Ÿ 用电话号码替换“*”部分就可以查到所需数据,获取“display_name”可以获取到联系人显示名
- 添加联系人
Ÿ 先向raw_contacts表插入id,路径为:raw_contacts
Ÿ 得到id之后再向data表插入数据,路径为:data
- 使用事务添加联系人
Ÿ 在添加联系人得时候是分多次访问Provider,如果在过程中出现异常,会出现数据不完整的情况,这些操作应该放在一次事务中
Ÿ 使用ContentResolver的applyBatch(String authority,ArrayList<ContentProviderOperation> operations) 方法可以将多个操作在一个事务中执行
Ÿ 文档位置:
8. 网络通信
8.1. 获取文本数据
Ÿ 通过URL对象封装地址,打开一个HttpURLConnection
Ÿ 设置头信息之后获取响应码,如果成功返回200即可从HttpURLConnection中获取输入流读取数据
Ÿ 代码过长屏幕显示不全可以使用<ScrollView>进行显示
Ÿ 需要访问网络的权限
<uses-permission android:name="android.permission.INTERNET" />
8.2. 获取网络图片
Ÿ 通过BitmapFactory的decodeByteArray(byte[] data, int offset, int length)方法将数据转换为图片对象
8.3. 获取XML
Ÿ 使用URL封装路径,打开一个HttpURLConnection
Ÿ 设置头信息之后获取相应码,从输入流中获取数据
Ÿ 使用XmlPullPaser解析
8.4. 获取JSON
Ÿ 使用URL封装路径,打开一个HttpURLConnection
Ÿ 设置头信息之后获取相应码,从输入流中获取数据
Ÿ 将数据转为String,封装成JSONArray对象
Ÿ 遍历JSONArray对象,调用获取其中的JSONObject
Ÿ 再从JSONObject中获取每个字段的信息
8.5. 发送GET请求
Ÿ 拼接路径和参数,通过URL进行封装,打开一个HttpURLConnection,发送请求
Ÿ 如果参数是中文会出现乱码
Ÿ URL中包含的中文参数需要使用URLEncoder进行编码
Ÿ 服务器端如果是TOMCAT,其默认使用ISO8859-1编码,接收时需要处理编码问题
8.6. 发送POST请求
Ÿ 通过URL打开一个HttpURLConnection
Ÿ 头信息中除了超时时间和请求方式之外还必须设置Content-Type和Content-Length
Ÿ 从HttpURLConnection获得输出流输出参数数据
Ÿ 服务端可以使用request对象的setCharacterEncoding方法设置编码
8.7. 发送XML,访问WebService
- 发送XML
Ÿ 通过URL封装路径打开一个HttpURLConnection
Ÿ 设置请求方式,Content-Type和Content-Length
XML文件的Content-Type为:text/xml; charset=UTF-8
Ÿ 使用HttpURLConnection获取输出流输出数据
- WebService
Ÿ WebService是发布在网络上的API,可以通过发送XML调用,WebService返回结果也是XML数据
Ÿ WebService没有语言限制,只要可以发送XML数据和接收XML数据即可
Ÿ http://www.webxml.com.cn 网站上提供了一些WebService服务,我们可以对其进行调用
Ÿ http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx?op=getMobileCodeInfo 中提供了电话归属地查询的使用说明
8.8. HTTP协议上传文件
Ÿ 搭建服务器,完成上传功能
Ÿ 使用浏览器上传,查看请求信息
- HttpURLConnection
Ÿ 通过URL封装路径打开一个HttpURLConnection
Ÿ 设置请求方式以及头字段:Content-Type、Content-Length、Host
Ÿ 拼接数据发送
- Socket
Ÿ 使用HttpURLConnection发送时内部有缓存机制,如果上传较大文件会导致内存溢出
Ÿ 我们可以使用Socket发送TCP请求,将上传数据分段发送
- HttpClient
public void upload(String name, String password, String path) throws Exception {
// 创建HttpClient对象
HttpClient client = new HttpClient();
// 设置超时事件
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
// 创建一个Post请求, 指定路径
PostMethod postMethod = new PostMethod("http://192.168.1.102:8080/14.Web/LoginServlet");
// 封装每个表单项
Part[] parts = { new StringPart("name", name), new StringPart("password", password), new FilePart("file", new File(path)) };
// 给Post请求设置实体
postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams()));
// 执行Post请求
client.executeMethod(postMethod);
// Post请求是释放资源
postMethod.releaseConnection();
}
8.9. 多线程断点续传下载器
Ÿ 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度
Ÿ 手机端下载数据时难免会出现无信号断线、电量不足等情况,所以需要断点续传功能
Ÿ 根据下载数据长度计算每个线程下载的数据位置,程序中开启多个线程并发下载
在请求头中设置Range字段就可以获取指定位置的数据,例如:Range: bytes=100-200
Ÿ 在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,下次启动时从记录位置继续下载
- 多线程下载
Ÿ 进度条使用<Progress>进行配置
默认为圆形进度条,水平进度条需要配置style属性,?android:attr/progressBarStyleHorizontal
使用android.R.attr.progressBarStyleHorizontal作为样式
Ÿ 当点击下载按钮时开启多线程下载,下载过程中修改进度条进度
设置最大刻度:setMax()
设置当前进度:setProgress()
- 断点续传
Ÿ 断点续传需要在下载过程中记录每条线程的下载进度
Ÿ 每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库
Ÿ 在每次向文件中写入数据之后,在数据库中更新下载进度
Ÿ 下载完成之后删除数据库中下载记录
- Handler传输数据
Ÿ 主线程中创建的View只能在主线程中修改,其他线程只能通过和主线程通信,在主线程中改变View数据
Ÿ 我们使用Handler可以处理这种需求
主线程中创建Handler,重写handleMessage()方法
新线程中使用Handler发送消息,主线程即可收到消息,并且执行handleMessage()方法
- 动态生成新View
Ÿ 创建XML文件,将要生成的View配置好
Ÿ 获取系统服务LayoutInflater,用来生成新的View
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
Ÿ 使用inflate(int resource, ViewGroup root)方法生成新的View
Ÿ 调用当前页面中某个容器的addView,将新创建的View添加进来
9. 活动(Activity)
9.1. 创建Activity
- 定义Activity
Ÿ 定义类继承Activity
Ÿ 在AndroidManifest.xml的<application>节点中声明<activity>
- 显式意图创建方式
Ÿ 构造函数,代码少
new Intent(this, NewActivity.class);
Ÿ 类名形式,灵活,可扩展性强
intent.setClassName(this, "cn.itcast.activity.NewActivity");
Ÿ 包名类名形式,可启动其他程序中的Activity
intent.setClassName("cn.itcast.downloader", "cn.itcast.downloader.MainActivity");
- 创建Activity并传递数据
Ÿ 在意图对象中封装了一个Bundle对象,可以用来携带数据
Ÿ 在新Activity中可以获得意图对象以获取其中Bundle保存的数据
- 创建Activity获取返回数据
Ÿ 使用startActivityForResult(Intent intent, int requestCode) 方法打开Activity
Ÿ 重写onActivityResult(int requestCode, int resultCode, Intent data) 方法
Ÿ 新Activity中调用setResult(int resultCode, Intent data) 设置返回数据之后,关闭Activity就会调用onActivityResult方法
- 隐式意图创建Activity
Ÿ 显式意图是指在创建意图时指定了组件,而隐式意图则不指定组件,通过动作、类型、数据匹配对应的组件
Ÿ 在清单文件中定义<activity>时需要定义<intent-filter>才能被隐式意图启动
Ÿ <intent-filter>中至少配置一个<action>和一个<category>,否则无法被启动
Ÿ Intent对象中设置的action、category、data在<intent-filter>必须全部包含才能启动
Ÿ <intent-filter>中的<action>、<category>、<data>都可以配置多个,Intent对象中不用全部匹配,每样匹配一个即可启动
Ÿ 如果一个意图可以匹配多个Activity,Android系统会提示选择
9.2. 生命周期
Ÿ Acitivity三种状态
运行:activity在最前端运行
暂停:activity可见,但前端还有其他acti vity,被覆盖一部分,或者前端activity透明
停止:activity不可见,完全被覆盖
Ÿ 生命周期相关方法
onCreate:创建时调用,或者程序在暂停、停止状态下被杀死之后重新打开时也会调用
onStart:onCreate之后或者从停止状态恢复时调用
onResume:onStart之后或者从暂停状态恢复时调用,从停止状态恢复时由于调用onStart,也会调用onResume
onPause:进入暂停、停止状态,或者销毁时会调用
onStop:进入停止状态,或者销毁时会调用
onDestroy:销毁时调用
onRestart:从停止状态恢复时调用
Ÿ 保存信息相关方法
onSaveInstanceState:在Activity被动的摧毁或停止的时候调用,用于保存运行数据,可以将数据存在在Bundle中
onRestoreInstanceState:该方法在Activity被重新绘制的时候调用,例如改变屏幕方向,savedInstanceState为onSaveInstanceState保存的数据
9.3. 启动模式
Ÿ 在AndroidManifest.xml中的<activity>标签中可以配置android:launchMode属性,用来控制Actvity的启动模式
Ÿ 在Android系统中我们创建的Acitivity是以栈的形式呈现的
standard:每次调用startActivity()启动时都会创建一个新的Activity放在栈顶
singleTop:如果启动的Activity时,指定Activity不在栈顶就创建,如在栈顶,则不再创建
singleTask:如果启动的Activity不存在就创建,如果存在直接跳转到指定的Activity所在位置
singleInstance:如果启动的Activity不存在就创建,如果存在就将指定的Activity移动到栈顶
9.4. 内存管理
Ÿ Android系统在运行多个进程时,如果系统资源不足,会强制结束一些进程。优先选择哪个进程来结束是有优先级的。以下顺序靠上的优先结束
空:进程中所有Activity都已销毁
后台:进程中有一个停止状态的Activity
可见:进程中有一个暂停状态的Activity
前台:进程中正在运行一个Activity
10. 广播接收者(BroadcastReceiver)
10.1. 定义广播接收者
Ÿ 定义类继承BroadcastReceiver,重写onReceive方法
Ÿ 清单文件中声明<receiver>,需要在其中配置<intent-filter>指定接收广播的动作
Ÿ 当接收到匹配广播之后就会执行onReceive方法
Ÿ BroadcastReceiver除了在清单文件中声明,也可以在代码中声明,使用registerReceiver方法注册Receiver
10.2. 发送广播
- 无序广播
Ÿ 使用sendBroadcast方法发送
Ÿ 被所有广播接收者接收,无序,不可中断
Ÿ 广播时可设置接收者权限,仅当接收者含有权限才能接收
Ÿ 接收者的<receiver>也可设置发送方权限,只接收含有权限应用的广播
- 有序广播
Ÿ 使用sendOrderedBroadcast方法发送
Ÿ 接收者可以在<intent-filter>中定义android:priority定义优先级,数字越大优先级越高
Ÿ 被各个广播接收者逐个接收,中途可以中断或者添加数据
abortBroadcast()
getResultExtras(true).putString("data", "新增数据");
10.3. 监听短信接收
Ÿ Android系统在收到短信的时候会发送一条有序广播,我们如果定义一个接收者接收这个广播,就可以得到短信内容,也可以拦截短信
Ÿ 定义广播接收者接收广播android.provider.Telephony.SMS_RECEIVED
Ÿ 在onReceive方法内部调用Intent的getExtras()再调用get(String)获取其中pdus字段,得到一个Object[],其中每一个元素都是一个byte[]
Ÿ 通过SmsMessage类的createFromPdu方法创建SmsMessage对象
Ÿ 从SmsMessage对象中即可获取发送者号码、短信内容、发送时间等信息
Ÿ 需要接收短信权限:<uses-permission android:name="android.permission.RECEIVE_SMS"/>
Ÿ Android系统中收到短信的通知是一个有序通知,我们如需拦截垃圾短信,可以配置较高的priority,收到信息进行判断是否abortBroadcast()
10.4. 监听呼出电话
Ÿ 定义广播接收者接收 android.intent.action.NEW_OUTGOING_CALL
Ÿ 需要权限 <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
Ÿ 在onReceive方法中使用getResultData() 和 setResultData() 方法获取和设置电话号码
10.5. 生命周期
Ÿ 广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁
Ÿ 广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框
Ÿ 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉
Ÿ 耗时的较长的工作最好放在服务中完成
11. 服务(Service)
11.1. 基本概念
Ÿ Service是一种在后台运行,没有界面的组件,由其他组件调用开始。
Ÿ 创建Service,定义类继承Service,AndroidManifest.xml中定义<service>
Ÿ 开启Service,在其他组件中调用startService方法
Ÿ 停止Service,调用stopService方法
11.2. 电话录音
需要权限:android.permission.READ_PHONE_STATE
TelephonyManager manager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
manager.listen(new MyListener(), PhoneStateListener.LISTEN_CALL_STATE);
private final class MyListener extends PhoneStateListener {
private String num;
private MediaRecorder recorder;
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
num = incomingNumber;
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
try {
File file = new File(Environment.getExternalStorageDirectory(), num + "_" + System.currentTimeMillis() + ".3gp");
recorder = new MediaRecorder();
recorder.setAudioSource(AudioSource.MIC);
recorder.setOutputFormat(OutputFormat.THREE_GPP);
recorder.setAudioEncoder(AudioEncoder.AMR_NB);
recorder.setOutputFile(file.getAbsolutePath());
recorder.prepare();
recorder.start();
} catch (Exception e) {
e.printStackTrace();
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if (recorder != null) {
recorder.stop();
recorder.release();
}
break;
}
}
}
11.3. 绑定本地服务
Ÿ 使用bindService绑定服务,传入一个自定义的ServiceConnection用来接收IBinder
Ÿ 定义一个业务接口,其中定义需要的使用的方法
Ÿ 服务中自定义一个IBinder继承Binder并实现业务接口,在onBind方法中返回
Ÿ 调用端将IBinder转为接口类型,调用接口中的方法即可调用到服务中的方法
11.4. 绑定远程服务
Ÿ 远程绑定服务时无法通过同一个接口来调用方法,这时就需要使用AIDL技术
Ÿ 将接口扩展名改为“.aidl”
Ÿ 去掉权限修饰符
Ÿ gen文件夹下会生成同名接口
Ÿ 将服务中自定义的IBinder类改为继承接口中的Stub
Ÿ ServiceConnection中返回的IBinder是代理对象,不能使用强转,改用Stub.asInterface()
11.5. AIDL使用自定义类型
Ÿ AIDL默认只能使用Java中基本数据类型和String、List、Map,List和Map中的元素类型也只能是这些类型。
Ÿ 如果需要使用其他类型数据,使用的类必须实现Parcelable接口以完成序列化和反序列化工作
重写 public void writeToParcel(Parcel dest, int flags)
定义 public static final Parcelable.Creator<Person> CREATOR
Ÿ 定义该类对应的AIDL
package 包名
parcelable 类名
Ÿ 在接口AIDL中导入该类,注意:即使是同一个包也需要导入
12. 多媒体
12.1. 音频播放器
12.2. 视频播放器
screenSV.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // 设置缓冲区数据
screenSV.getHolder().setKeepScreenOn(true); // 设置屏幕保持
screenSV.getHolder().addCallback(new MyCallback()); // 设置回调函数
player.reset();
player.setDisplay(screenSV.getHolder()); // 设置显式
player.setDataSource("/mnt/sdcard/1.mp4"); // 设置数据源
player.prepare(); // 准备
player.seekTo(position); // 跳转到指定位置
player.start();
12.3. 拍照
Ÿ 需要权限
<uses-permission android:name="android.permission.CAMERA" />
Ÿ 打开摄像头
Camera.open()
SDK2.3之后支持前置摄像头,open方法可以接收一个int参数,用来指定哪个摄像头
Ÿ 设置预览显示位置
setPreviewDisplay(SurfaceHolder holder)
注意SurfaceView不在前端显示的时候会被销毁,恢复之后会重绘
Ÿ 开始预览
startPreview()
将摄像头拍摄画面显示在SurfaceView中,在此之前可对摄像头进行参数配置
getParameters() 方法可以获取摄像头的相关参数Parameters,调用其内部方法即可进行配置
Ÿ 自动对焦
autoFocus(AutoFocusCallback cb)
自动对焦是一个异步操作,如果我们向等待自动对焦结束之后才开始拍照,需要传入一个回调对象,在其回调函数中调用拍照方法
Ÿ 拍照
takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg)
拍照也是异步操作,需要通过回调函数来得到拍照之后的数据
注意拍照之后摄像头不回自动回到预览状态,需要重写调用startPreview()方法
12.4. 录像
Ÿ 需要权限
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
Ÿ 创建MediaRecorder
new MediaRecorder()
Ÿ 设置音频输入源
setAudioSource(int audio_source)
Ÿ 设置视频输入源
setVideoSource(int video_source)
Ÿ 设置输出格式
setOutputFormat(int output_format)
Ÿ 设置音频编码器
setAudioEncoder(int audio_encoder)
Ÿ 设置视频编码器
setVideoEncoder(int video_encoder)
Ÿ 设置预览显示位置
setPreviewDisplay(Surface sv)
Ÿ 设置输出文件
setOutputFile(String path)
Ÿ 准备录制
prepare()
Ÿ 开始录制
start()
开始录制之前需要结束摄像头的预览
Ÿ 结束录制释放资源
stop()
release()
13. 通知
13.1. 吐司通知
Ÿ 创建通知
Toast.makeText(Context context, CharSequence text, int duration)
Toast.makeText(Context context, int resId, int duration)
Ÿ 发送通知
show()
13.2. 状态栏通知
Ÿ 获取系统通知服务
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE)
Ÿ 创建通知
通过构造函数创建: Notification(int icon, CharSequence tickerText, long when)
icon: 通知的图片资源ID
tickerText: 状态栏中显示的消息内容
when: 时间
Ÿ 创建PendingIntent以供点击时发送
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
context: 当前上下文
requestCode: 请求码
intent: 点击时要发送的意图
flags: 类型, PendingIntent中提供了常量选择
Ÿ 设置通知点击事件
调用Notification 对象方法: setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)
context: 当前上下文
contentTitle: 标题
contentText: 内容
contentIntent: 点击时触发的意图
Ÿ 设置通知点击后清除
设置Notification 对象属性 n.flags = Notification.FLAG_AUTO_CANCEL;
Ÿ 发送消息
调用Notification对象方法: notify(int id, Notification notification)
13.3. 对话框通知
- 普通对话框
new AlertDialog.Builder(this) //
.setTitle("普通对话框") //
.setMessage("普通内容") //
.setCancelable(false) //
.setPositiveButton("YES", listener) // listener 为OnClickListener 监听器对象, 监听按钮被选中
.setNeutralButton("CANCEL", listener) //
.setNegativeButton("NO", listener) //
.show();
- 列表对话框
new AlertDialog.Builder(this) //
.setTitle("列表对话框") //
.setCancelable(false) //
.setItems(items, listener) // listener 为OnClickListener 监听器对象, 监听列表项被选中
.show();
- 单选对话框
new AlertDialog.Builder(this) //
.setTitle("单选对话框") //
.setCancelable(false) //
.setSingleChoiceItems(items, 0, choiceLinstener) // 0, 为默认选中索引, choiceLinstener 为 OnClickListener 监听器对象, 监听单选按钮被选中
.setPositiveButton("确定", positiveLinstener) // positiveLinstener 为 OnClickListener 监听器对象, 监听确定按钮点击
.show();
- 多选对话框
new AlertDialog.Builder(this) //
.setTitle("多选对话框") //
.setCancelable(false) //
.setMultiChoiceItems(items, checkedArr, choiceListener) // checkedArr 为默认选中, choiceListener 为 OnMultiChoiceClickListener 监听器对象, 监听多选按钮被选中
.setPositiveButton("确定", positiveLinstener) // positiveLinstener 为 OnClickListener 监听器对象, 监听确定按钮点击
.show();
- 进度对话框
ProgressDialog dialog = new ProgressDialog(this);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // 设置进度条样式
dialog.setTitle("下载中");
dialog.setMessage("请稍候...");
dialog.setCancelable(false);
dialog.setMax(100);
dialog.show();
dialog.setProgress(10); // 设置进度
dialog.dismiss(); // 对话框结束
关于通知的文档位置:android-sdk-windows/docs/guide/topics/ui/notifiers/index.html
14. 常用UI
14.1. 列表视图(ListView)
- XML配置
Ÿ 在主界面中配置<ListView>标签
Ÿ 在res/layout/文件夹下创建一个新的xml文件指定每个条目的布局
- Java代码构建ListView
Ÿ 获取ListView对象
Ÿ 设置一个Adapter
BaseAdapter:实现内部抽象方法
SimpleAdapter:以List<Map<String, ?>>形式封装数据
SimpleCursorAdapter:以Cursor对象封装数据,Cursor中需要有“_id”一列
Ÿ 添加OnItemClickListener
调用ListView的getItemAtPosition(int)方法可以获取封装数据的容器
如果传入的是BaseAdapter,获取到的就是我们自定义方法中返回的内容
如果传入的是SimpleAdapter,获取到的就是一个Map<String, ?>
如果传入的是SimpleCursorAdapter,获得到的就是一个Cursor,并且Cursor以指向选中的一条记录
14.2. 单选(RadioGroup)
Ÿ 定义<RadioGroup>
Ÿ 在<RadioGroup>中定义<RadioButton>和<Button>
Ÿ 处理Button的点击事件
Ÿ 根据ID获取RadioGroup对象,调用其getCheckedRadioButtonId()方法可以获取其中被选中的RadioGroup的ID
- 代码
<RadioGroup
android:id="@+id/lessonsRG"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/javaRB"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Java" />
<RadioButton
android:id="@+id/netRB"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=".Net" />
<RadioButton
android:id="@+id/phpRB"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="PHP" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onRradioClick"
android:text="确定" />
</RadioGroup>
public void onRradioClick(View view) {
RadioGroup lessonRG = (RadioGroup) findViewById(R.id.lessonsRG);
int id = lessonRG.getCheckedRadioButtonId(); // 获取选中的id
String msg = null;
switch (id) {
case R.id.javaRB:
msg = "Java";
break;
case R.id.netRB:
msg = ".Net";
break;
case R.id.phpRB:
msg = "PHP";
break;
}
Toast.makeText(this, msg, 0).show();
}
14.3. 多选(CheckBox)
Ÿ 定义若干<CheckBox>和一个<Button>
Ÿ 处理Button的点击事件
Ÿ 根据ID获取每个CheckBox,调用其isChecked()方法判断是否被选中
- 代码
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<CheckBox
android:id="@+id/javaCB"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Java" />
<CheckBox
android:id="@+id/netCB"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=".Net" />
<CheckBox
android:id="@+id/phpCB"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="PHP" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="checkboxOnClick"
android:text="确定" />
</LinearLayout>
public void checkboxOnClick(View view) {
CheckBox javaCB = (CheckBox) findViewById(R.id.javaCB);
CheckBox netCB = (CheckBox) findViewById(R.id.netCB);
CheckBox phpCB = (CheckBox) findViewById(R.id.phpCB);
StringBuilder sb = new StringBuilder();
sb.append(javaCB.isChecked() ? javaCB.getText() + " " : "");
sb.append(netCB.isChecked() ? netCB.getText() + " " : "");
sb.append(phpCB.isChecked() ? phpCB.getText() + " " : "");
Toast.makeText(this, sb, 0).show();
}
14.4. 下拉列表(Spinner)
Ÿ 定义<Spinner>标签
Ÿ 创建一个适配器
Ÿ 获取Spinner标签,调用setAdapter(SpinnerAdapter adapter)方法设置一个适配器
Ÿ 调用setOnItemSelectedListener(OnItemSelectedListener listener)方法设置监听器监听选中事件
- XML配置
<Spinner
android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
- 使用字符串构建适配器
private void setSpinnerByString() {
final Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item); // 设置样式
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置下拉后样式
adapter.add("Java");
adapter.add(".Net");
adapter.add("PHP");
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selection = (String) spinner.getItemAtPosition(position);
Toast.makeText(getApplicationContext(), selection, 0).show();
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
- 使用JavaBean构建适配器
private void setSpinnerByJavaBean() {
final Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<User> adapter = new ArrayAdapter<User>(this, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter.add(new User(1, "lhm", "lhm@itcast.cn"));
adapter.add(new User(2, "yzk", "yzk@itcast.cn"));
adapter.add(new User(3, "hsp", "hsp@itcast.cn"));
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
User selection = (User) spinner.getItemAtPosition(position);
Toast.makeText(getApplicationContext(), selection.getName(), 0).show();
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
- 使用资源文件构建适配器
<string-array name="items">
<item>Java</item>
<item>.Net</item>
<item>PHP</item>
</string-array>
private void setSpinnerByResource() {
final Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.items, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
CharSequence selection = (CharSequence) spinner.getItemAtPosition(position);
Toast.makeText(getApplicationContext(), selection, 0).show();
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
- 自定义适配器样式
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@android:drawable/ic_delete" />
<TextView
android:id="@+id/content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="50sp" />
</LinearLayout>
private void setSpinnerByCustom() {
final Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(this, R.layout.item, R.id.content);
adapter.add("Java");
adapter.add(".Net");
adapter.add("PHP");
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selection = (String) spinner.getItemAtPosition(position);
Toast.makeText(getApplicationContext(), selection, 0).show();
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
14.5. 菜单(Menu)
- 添加菜单项
Ÿ 重写Actvity的onCreateOptionsMenu(Menu menu)方法
Ÿ 添加菜单项
调用方法中参数menu的add(CharSequence title) 方法
Ÿ 添加子菜单
调用menu对象的addSubMenu(final CharSequence title)
该方法返回一个SubMenu对象
Ÿ 添加子菜单的菜单项
调用SubMenu对象的add(CharSequence title) 方法
- 处理菜单点击事件
Ÿ 重写Activity的onOptionsItemSelected(MenuItem item) 方法
参数item即为被选中的菜单项
- 代码
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("增加");
menu.add("修改");
menu.add("删除");
SubMenu subMenu = menu.addSubMenu("查询");
subMenu.add("按照序号查询");
subMenu.add("按照姓名查询");
subMenu.add("按照邮箱查询");
return super.onCreateOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem item) {
Toast.makeText(this, item.getTitle(), 0).show();
return super.onOptionsItemSelected(item);
}
14.6. 内容提示文本框(AutoCompleteTextView)
- 单次提示
- 代码
<AutoCompleteTextView
android:id="@+id/actv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:completionThreshold="1" />
private void setAutoCompleteTextView() {
AutoCompleteTextView actv = (AutoCompleteTextView) findViewById(R.id.actv);
String[] items = { "tom", "tony", "terry", "张孝祥", "张海军", "张泽华" };
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, items);
actv.setAdapter(adapter);
}
- 多次提示
- 代码
<MultiAutoCompleteTextView
android:id="@+id/mactv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:completionThreshold="1" />
private void setMultiAutoCompleteTextView() {
MultiAutoCompleteTextView mactv = (MultiAutoCompleteTextView) findViewById(R.id.mactv);
String[] items = { "tom", "tony", "terry", "张孝祥", "张海军", "张泽华" };
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, items);
mactv.setAdapter(adapter);
mactv.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
}
14.7. 手势识别(GestureOverlayView)
- 创建手势库
Ÿ 导入SDK中的工程
android-sdk-windows\samples\android-8\GestureBuilder
这个工程不能直接导入,需要添加三个配置文件:.classpath、.project、default.properties
Ÿ 将工程部署到手机中,创建手势库
手势库会存储在手机SD卡的根目录,文件名为:gestures
- 代码
将gestures放入res/raw文件夹下
<android.gesture.GestureOverlayView
android:id="@+id/gov"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gestureStrokeType="multiple" />
GestureOverlayView gov = (GestureOverlayView) findViewById(R.id.gov);
final GestureLibrary library = GestureLibraries.fromRawResource(this, R.raw.gestures);
library.load();
gov.addOnGesturePerformedListener(new OnGesturePerformedListener() {
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
ArrayList<Prediction> list = library.recognize(gesture);
for (Prediction p : list)
System.out.println(p.name + ": " + p.score);
}
});
14.8. 网页视图(WebView)
- 代码
<WebView
android:id="@+id/webView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
WebView webView = (WebView) findViewById(R.id.webView);
webView.getSettings().setBuiltInZoomControls(true); // 放大缩小按钮
webView.getSettings().setJavaScriptEnabled(true); // JS允许
webView.setWebChromeClient(new WebChromeClient()); // Chrome内核
webView.loadUrl("http://192.168.1.100:8080");
15. 样式与主题
15.1. 样式
- 定义样式
Ÿ 设置样式,在values文件夹下的任意文件中的<resources>中配置<style>标签
<style name="style1">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
Ÿ 继承样式,在<style>标签中配置属性parent
<style name="style2" parent="@style/style1">
<item name="android:textColor">#FF0000</item>
</style>
Ÿ 继承样式,在name中引用其他样式
<style name="style2.style3">
<item name="android:textSize">30sp</item>
</style>
- 使用样式
Ÿ 在layout文件的标签中配置style属性
<Button
style="@style/style2.style3"
android:text="这是一个按钮"
/>
15.2. 主题
Ÿ 定义过的样式也可以应用在<activity>和<application>标签中,使用theme属性尽心配置
<style name="theme">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">?android:windowNoTitle</item>
</style>
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/theme"
>
Ÿ ? 表示引用其他属性的值
Ÿ @ 表示访问资源文件
Ÿ 如果使用android内置的样式,IDE自动提示的“_”要替换成“.”
16. 国际化与屏幕适配
16.1. 国际化
Ÿ 在values和drawable文件夹后加上语言以及地区名,程序中需要国际化的部分使用资源ID
values-en-rUK
values-en-rUS
values-zh-rCN
values-zh-rTW
Ÿ 匹配规则
在匹配资源时先会找语言、地区完全匹配的
如果没有地区匹配的,则查找语言匹配的
如果没有语言匹配的则找默认values
16.2. 屏幕适配
Ÿ 在layout文件夹后加上分辨率,系统会根据屏幕尺寸自动选择
注意分辨率中的乘号是“x”不是“*”
Ÿ 如果没有匹配的分辨率会找默认layout文件夹
17. 动画特效
17.1. Frame
Ÿ 通过多个画面连续播放实现动画效果
Ÿ 详见文档 android-sdk-windows/docs/guide/topics/resources/animation-resource.html
17.2. Tween
Ÿ 将某个组件以渐变的方式实现透明、缩放、移动、旋转等动画效果
Ÿ 详见文档 android-sdk-windows/docs/guide/topics/resources/animation-resource.html
17.3. 使用动画切换Activity
Ÿ 在startActivity()方法调用之后调用overridePendingTransition(int enterAnim, int exitAnim)方法
enterAnim 进入的动画资源id
exitAnim 退出的动画 资源id
17.4. 使用动画翻页
Ÿ XML配置
<ViewFlipper
android:id="@+id/viewFlipper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/bb2"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/bb3"
/>
</ViewFlipper>
Ÿ Java代码
public boolean onTouchEvent(MotionEvent event) {
ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
start = event.getX();
break;
case MotionEvent.ACTION_UP:
float end = event.getX();
if (end > start) {
viewFlipper.setInAnimation(this, R.anim.previous_enter);
viewFlipper.setOutAnimation(this, R.anim.previous_exit);
viewFlipper.showPrevious();
} else if (end < start) {
viewFlipper.setInAnimation(this, R.anim.next_enter);
viewFlipper.setOutAnimation(this, R.anim.next_exit);
viewFlipper.showNext();
}
break;
}
return super.onTouchEvent(event);
}
18. 其他
18.1. 传感器
- 传感器参数
Ÿ 传感器类型
方向 Sensor.TYPE_ORIENTATION
加速 Sensor.TYPE_ACCELEROMETER
光线 Sensor.TYPE_LIGHT
磁场 Sensor.TYPE_MAGNETIC_FIELD
距离 Sensor.TYPE_PROXIMITY
温度 Sensor.TYPE_TEMPERATURE
Ÿ 传感器反应速度
SensorManager.SENSOR_DELAY_FASTEST
SensorManager.SENSOR_DELAY_GAME
SensorManager.SENSOR_DELAY_UI
SensorManager.SENSOR_DELAY_NORMAL
- 使用方向传感器
Ÿ 获得传感器管理器
SensorManager manager = (SensorManager) getSystemService(SENSOR_SERVICE);
Ÿ 获得方向传感器
Sensor sensor = manager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
Ÿ 注册监听器
manager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
Ÿ 监听器
private final class MySensorEventListener implements SensorEventListener {
public void onSensorChanged(SensorEvent event) {
System.out.println(event.values[0]);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
Ÿ 取消监听器
manager.unregisterListener(listener, sensor);
18.2. 触摸事件
- 拖拽
Ÿ XML配置
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="matrix"
android:src="@drawable/image" />
Ÿ Java代码
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setOnTouchListener(new MyOnTouchListener());
private class MyOnTouchListener implements OnTouchListener {
private float x;
private float y;
private Matrix currentMatrix = new Matrix(); // 用来操作图片的矩阵
private Matrix oldMatrix = new Matrix();
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // 按下时
x = event.getX(); // 获取x轴坐标
y = event.getY(); // 获取y轴坐标
oldMatrix.set(imageView.getImageMatrix()); // 记住位置
break;
case MotionEvent.ACTION_MOVE: // 移动时
currentMatrix.set(oldMatrix); // 设置成按下时记住的位置
currentMatrix.postTranslate(event.getX() - x, event.getY() - y); // 改变位置
break;
}
imageView.setImageMatrix(currentMatrix); // 移动图片
return true;
}
}
- 多点触摸
private class MyOnTouchListener implements OnTouchListener {
private float x; // 图片移动前的x轴坐标
private float y; // 图片移动前的y轴坐标
private Matrix currentMatrix = new Matrix(); // 用来移动图片的矩阵
private Matrix oldMatrix = new Matrix(); // 图片移动前的矩阵
private int type; // 操作类型, 一根手指触摸还是两根手指触摸
private float start; // 第二根手指按下时的距离
private float end; // 两根手指移动后的距离
private PointF point; // 放大时的中心点
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
type = 1;
x = event.getX();
y = event.getY();
oldMatrix.set(imageView.getImageMatrix());
break;
case MotionEvent.ACTION_MOVE:
currentMatrix.set(oldMatrix);
if (type == 1) { // 1根手指触摸
currentMatrix.postTranslate(event.getX() - x, event.getY() - y);
} else { // 2跟手指触摸
end = countDistance(event); // 计算结束时距离
float scale = end / start; // 计算缩放比例
currentMatrix.postScale(scale, scale, point.x, point.y); // 设置缩放
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
type = 2;
start = countDistance(event); // 计算开始时距离
point = countPoint(event); // 计算中心点
oldMatrix.set(imageView.getImageMatrix());
break;
}
imageView.setImageMatrix(currentMatrix); // 改变图片
return true;
}
}
public float countDistance(MotionEvent event) {
float a = event.getX(1) - event.getX(0); // x轴距离
float b = event.getY(1) - event.getY(0); // y轴距离
return (float) Math.sqrt(a * a + b * b); // 勾股定理
}
public PointF countPoint(MotionEvent event) {
float x = (event.getX(0) + event.getX(1)) / 2; // x轴中间点
float y = (event.getY(0) + event.getY(1)) / 2; // y轴中间点
return new PointF(x, y);
}
18.3. 读取SIM卡
- 电话号码、运营商信息
Ÿ 需要权限
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Ÿ Java代码
TelephonyManager manager = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
System.out.println("电话号码: " + manager.getLine1Number());
System.out.println("运营商编号: " + manager.getNetworkOperator());
System.out.println("运营商名字: " + manager.getNetworkOperatorName());
- 联系人
Ÿ 需要权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
Ÿ Java代码
Uri uri = Uri.parse("content://icc/adn");
Cursor c = getContentResolver().query(uri, null, null, null, null);
while (c.moveToNext())
System.out.println(c.getString(c.getColumnIndex("name")) + ": " + c.getString(c.getColumnIndex("number")));
- 通话记录
Ÿ 需要权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
Ÿ Java代码
Uri uri = CallLog.Calls.CONTENT_URI;
Cursor c = getContentResolver().query(uri, null, null, null, null);
while (c.moveToNext())
System.out.println(c.getString(c.getColumnIndex("number")) + ": " + c.getString(c.getColumnIndex("type")));
Ÿ 源代码
ContactsProvider\src\com\android\providers\contacts\ CallLogProvider.java
18.4. 安装程序
Ÿ 需要权限
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
Ÿ Java代码
File file = new File(Environment.getExternalStorageDirectory(), "test.apk");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
18.5. 关闭程序
Ÿ 杀死当前进程
Process.killProcess(Process.myPid());
Ÿ 退出虚拟机
System.exit(0);
Ÿ 根据包名关闭后台进程
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
manager.restartPackage("cn.itcast.test");
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
18.6. 使用HTML构建界面
Ÿ HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
function show(jsondata) {
var jsonobjs = eval(jsondata);
var table = document.getElementById("personTable");
for ( var y = 0; y < jsonobjs.length; y++) {
var tr = table.insertRow(table.rows.length);
var td1 = tr.insertCell(0);
var td2 = tr.insertCell(1);
td2.align = "center";
var td3 = tr.insertCell(2);
td3.align = "center";
td1.innerHTML = jsonobjs[y].name;
td2.innerHTML = jsonobjs[y].amount;
td3.innerHTML = "<a href=‘javascript:contact.call(\"" + jsonobjs[y].phone + "\")‘>" + jsonobjs[y].phone + "</a>";
}
}
</script>
</head>
<body onload="javascript:contact.showContacts()">
<table border="0" width="100%" id="personTable" cellspacing="0">
<tr>
<td width="30%">姓名</td>
<td width="30%" align="center">存款</td>
<td align="center">电话</td>
</tr>
</table>
</body>
</html>
Ÿ XML代码
<WebView
android:id="@+id/webView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
Ÿ Java代码
public class MainActivity extends Activity {
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
webView = (WebView) findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("file:///android_asset/index.html");
webView.addJavascriptInterface(new Contact(), "contact");
}
private final class Contact {
public void showContacts() {
String json = "[{\"name\":\"zxx\", \"amount\":\"99999\", \"phone\":\"18600012345\"}]";
webView.loadUrl("javascript:show(‘" + json + "‘)");
}
public void call(String phone) {
startActivity(new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phone)));
}
}
}
18.7. apk文件反编译
Ÿ 使用解压缩工具打开apk文件,找到其中dex文件
Ÿ 创建Java工程,导入dex2jar中的所有jar文件
Ÿ 创建运行环境运行其中pxb.android.dex2jar.v3.Main 类, 指定dex文件地址,会在同目录下生成jar文件
Ÿ 使用jd-gui打开jar文件获取源码