Android和PHP开发最佳实践
基本信息

【插图】

编辑推荐
51CTO社区、PHPChina社区强烈推荐!
国内第一本同时讲述Android客户端开发和PHP服务端开发的经典著作!
目前市面上第一本把Android移动互联网应用开发的完整解决方案分析透彻的书
相关推荐:
内容简介
书籍
计算机书籍
本书是国内第一本同时讲述Android客户端开发和PHP服务端开发的经典著作。
本书以一个完整的微博应用项目实例为主线,由浅入深地讲解了Android客户端开发和PHP服务端开发的思路和技巧。从前期的产品设计、架构设计,到客户端和服务端的编码实现,再到性能测试和系统优化,以及最后的打包发布,完整地介绍了移动互联网应用开发的过程。同时,本书也介绍了Android系统中比较有特色的功能,比如Google地图、LBS功能、传感器、摄像头、多媒体以及语音功能的使用等。此外,本书还介绍了Android
NDK的开发以及Android游戏开发的相关内容,包括OpenGL的使用、流行游戏引擎Cocos2d-x和Unity 3D。
本书适合于所有对Android和PHP技术有兴趣的读者。不管是客户端还是服务端的开发者,都可以从本书中获得不少有用的经验。另外,值得一提的是,全书绝大部分的实例代码均源自于真实项目,参考价值极高。
计算机书籍
本书是国内第一本同时讲述Android客户端开发和PHP服务端开发的经典著作。
本书以一个完整的微博应用项目实例为主线,由浅入深地讲解了Android客户端开发和PHP服务端开发的思路和技巧。从前期的产品设计、架构设计,到客户端和服务端的编码实现,再到性能测试和系统优化,以及最后的打包发布,完整地介绍了移动互联网应用开发的过程。同时,本书也介绍了Android系统中比较有特色的功能,比如Google地图、LBS功能、传感器、摄像头、多媒体以及语音功能的使用等。此外,本书还介绍了Android
NDK的开发以及Android游戏开发的相关内容,包括OpenGL的使用、流行游戏引擎Cocos2d-x和Unity 3D。
本书适合于所有对Android和PHP技术有兴趣的读者。不管是客户端还是服务端的开发者,都可以从本书中获得不少有用的经验。另外,值得一提的是,全书绝大部分的实例代码均源自于真实项目,参考价值极高。
作译者
黄隽实
国内*架构师,CSDN技术博客专家,具有多年大型互联网应用架构经验,通晓多门编程语言及相关技术,对Android平台和PHP语言有深入研究,近期专注于移动互联网领域的发展。参与的大型项目有:盛大手机游戏平台、ValueClick广告站群、EsteeLauder国际站群、Ihush特卖平台等。
国内*架构师,CSDN技术博客专家,具有多年大型互联网应用架构经验,通晓多门编程语言及相关技术,对Android平台和PHP语言有深入研究,近期专注于移动互联网领域的发展。参与的大型项目有:盛大手机游戏平台、ValueClick广告站群、EsteeLauder国际站群、Ihush特卖平台等。
目录
前言
第一篇 准 备 篇
第1章 学前必读 1
1.1 移动互联网时代的来临 1
1.2 为何选择Android和PHP 2
1.3 如何学习Android和PHP 3
1.3.1 如何学习Android 3
1.3.2 如何学习PHP 4
1.3.3 同时学好Android和PHP 4
1.4 小结 5
第2章 Android开发准备 6
2.1 Android背景知识 6
2.2 Android系统框架 8
2.3 Android应用框架 11
2.3.1 活动(Activity) 12
2.3.2 消息(Intent) 14
2.3.3 视图(View) 16
2.3.4 任务(Task) 17
2.4 Android系统四大组件 19
2.4.1 活动(Activity) 20
第一篇 准 备 篇
第1章 学前必读 1
1.1 移动互联网时代的来临 1
1.2 为何选择Android和PHP 2
1.3 如何学习Android和PHP 3
1.3.1 如何学习Android 3
1.3.2 如何学习PHP 4
1.3.3 同时学好Android和PHP 4
1.4 小结 5
第2章 Android开发准备 6
2.1 Android背景知识 6
2.2 Android系统框架 8
2.3 Android应用框架 11
2.3.1 活动(Activity) 12
2.3.2 消息(Intent) 14
2.3.3 视图(View) 16
2.3.4 任务(Task) 17
2.4 Android系统四大组件 19
2.4.1 活动(Activity) 20
2.4.2 服务(Service) 21
2.4.3 广播接收器(Broadcast Receiver) 23
2.4.4 内容提供者(Content Provider) 24
2.5 Android上下文 25
2.5.1 界面上下文(Activity Context) 25
2.5.2 应用上下文(Application Context) 27
2.6 Android数据存储 28
2.6.1 应用配置(Shared Preferences) 28
2.6.2 本地文件(Files) 29
2.6.3 数据库(SQLite) 30
2.7 Android应用界面 31
2.7.1 控件属性 31
2.7.2 布局(Layout) 33
2.7.3 事件(Event) 37
2.7.4 菜单(Menu) 39
2.7.5 主题(Theme) 41
2.7.6 对话框(Dialog) 42
2.8 Android图形界面 43
2.8.1 画笔(Paint) 43
2.8.2 画布(Canvas) 44
2.8.3 基础几何图形 46
2.8.4 常见图形变换 47
2.9 Android动画效果 50
2.9.1 逐帧动画(Frame Animation) 50
2.9.2 补间动画(Tween Animation) 51
2.10 Android开发环境 52
2.10.1 开发环境的搭建 53
2.10.2 首个Android项目 58
2.10.3 使用DDMS调试工具 63
2.11 小结 64
第3章 PHP开发准备 65
3.1 PHP开发基础 65
3.1.1 PHP语言简介 65
3.1.2 PHP语法简介 66
3.1.3 PHP开发起步 68
3.1.4 PHP面向对象编程 75
3.1.5 PHP的会话 78
3.2 PHP开发环境 80
3.2.1 开发环境的搭建 80
3.2.2 安装配置Xampp 81
3.2.3 管理Apache 83
3.2.4 管理MySQL 84
3.3 使用JSON通信 87
3.4 常用PHP开发框架 88
3.5 认识Smarty模板引擎 90
3.6 开发框架简介 93
3.6.1 框架的特点和优势 94
3.6.2 框架的基础目录结构 94
3.6.3 框架MVC思路讲解 97
3.6.4 框架MVC实例分析 99
3.7 小结 108
第二篇 实 战 篇
第4章 实例产品设计 109
4.1 为何选择微博 109
4.2 开发前的准备 110
4.2.1 选择开发模式 110
4.2.2 了解项目策划 111
4.2.3 了解原型设计 112
4.3 功能模块设计 112
4.4 应用界面设计 114
4.5 应用架构设计 115
4.6 通信协议定义 116
4.7 数据库结构设计 118
4.8 小结 120
第5章 程序架构设计 121
5.1 服务端程序架构设计 121
5.1.1 基础框架设计 122
5.1.2 调试框架设计 127
5.1.3 核心类库设计 130
5.1.4 服务端的MVC与SOA 136
5.2 客户端程序架构设计 136
5.2.1 基础框架设计 137
5.2.2 核心类包设计 138
5.2.3 Android应用的MVC 142
5.3 客户端界面架构设计 142
5.3.1 界面框架设计 143
5.3.2 主要界面设计 144
5.4 小结 146
第6章 服务端开发 147
6.1 开发入门 147
6.1.1 接口程序开发 147
6.1.2 调试框架开发 151
6.1.3 生成接口文档 155
6.2 验证接口 156
6.2.1 用户登录接口 156
6.2.2 用户登出接口 160
6.3 用户接口 162
6.3.1 新建用户接口 162
6.3.2 更新用户信息接口 164
6.3.3 查看用户信息接口 165
6.3.4 添加粉丝接口 167
6.3.5 删除粉丝接口 171
6.4 微博接口 172
6.4.1 发表微博接口 172
6.4.2 查看微博接口 174
6.4.3 微博列表接口 176
6.5 评论接口 180
6.5.1 发表评论接口 180
6.5.2 评论列表接口 182
6.6 图片接口 184
6.6.1 用户头像接口 185
6.6.2 头像列表接口 188
6.7 通知接口 189
6.8 Web版接口 192
6.8.1 Web版UI界面(jQuery Mobile) 193
6.8.2 Web版地图接口 195
6.9 小结 196
第7章 客户端开发 198
7.1 开发入门 198
7.1.1 开发思路梳理 198
7.1.2 掌握应用配置文件 200
7.1.3 常规程序开发与调试 211
7.2 界面布局和行为控制 221
7.2.1 使用Layout布局 221
7.2.2 使用Merge整合界面 224
7.2.3 使用Event控制用户行为 226
7.2.4 使用Intent控制界面切换 228
7.3 网络通信模块 231
7.3.1 使用HttpClient进行网络通信 231
7.3.2 支持CMWAP网络接入方式 237
7.3.3 使用JSON库为消息解码 240
7.3.4 使用Toast消息提示 245
7.4 异步任务模块 247
7.4.1 进程和线程 247
7.4.2 任务创建Thread 249
7.4.3 任务处理Handler 255
7.4.4 使用异步任务AsyncTask 257
7.5 全局功能模块 259
7.5.1 全局UI基类 259
7.5.2 全局Menu菜单 264
7.5.3 全局Dialog窗口 265
7.5.4 使用Service获取通知 266
7.5.5 使用Notification显示通知 270
7.6 用户登录界面 273
7.6.1 界面程序逻辑 273
7.6.2 使用TextView 273
7.6.3 使用EditText 274
7.6.4 使用Button 276
7.6.5 使用Shape和Selector 277
7.6.6 使用CheckBox 279
7.6.7 使用SharedPreference 280
7.7 微博列表界面 281
7.7.1 界面程序逻辑 281
7.7.2 使用ListView 286
7.7.3 使用ImageView 290
7.7.4 使用draw9patch 292
7.7.5 异步获取远程图片 294
7.7.6 使用SdCard缓存图片 297
7.7.7 使用SQLite缓存数据 300
7.8 我的微博列表 303
7.8.1 界面程序逻辑 303
7.8.2 使用ScrollView 309
7.8.3 使用自定义微博列表 310
7.9 微博文章界面 313
7.9.1 界面程序逻辑 313
7.9.2 界面布局进阶(综合使用UI控件) 319
7.9.3 发表评论功能实现 322
7.9.4 发表微博功能实现 326
7.10 用户配置界面 328
7.10.1 界面程序逻辑 328
7.10.2 使用自定义选项列表 333
7.10.3 修改签名功能实现 334
7.10.4 更换头像功能实现 334
7.11 网页界面开发 340
7.11.1 界面程序逻辑 340
7.11.2 使用WebView 341
7.11.3 使用ProgressDialog 343
7.11.4 使用WebView的重写和回调 346
7.11.5 网页地图实例分析 348
7.12 小结 349
第三篇 优 化 篇
第8章 性能分析 351
8.1 关于性能测试 351
8.1.1 服务端压力测试 352
8.1.2 客户端性能测试 359
8.2 瓶颈 364
8.2.1 服务端瓶颈分析 365
8.2.2 客户端瓶颈分析 366
8.3 优化的思路 366
8.4 小结 367
第9章 服务端优化 368
9.1 优化PHP程序 368
9.1.1 优化PHP代码 368
9.1.2 优化Session机制 371
9.1.3 使用缓存中间件 373
9.1.4 使用APC加速 376
9.2 优化数据传输 377
9.2.1 优化JSON协议 377
9.2.2 使用gzip压缩 379
9.3 其他优化 380
9.3.1 服务器优化 380
9.3.2 数据库优化 383
9.3.3 网络优化 386
9.4 小结 386
第10章 客户端优化 387
10.1 优化Android程序 387
10.1.1 优化Java代码 387
10.1.2 异步获取数据 391
10.1.3 文件资源缓存 391
10.1.4 数据库缓存 392
10.2 避免内存泄露 392
10.2.1 Android内存管理 392
10.2.2 如何判断内存泄露 393
10.2.3 常见内存泄露的处理 395
10.3 优化Android UI 396
10.3.1 模板代码优化 396
10.3.2 关于布局优化 397
10.3.3 使用Hierarchy Viewer工具 402
10.4 其他优化 403
10.4.1 优化图片 403
10.4.2 优化APK包 403
10.4.3 使用keytool和jarsigner签名 404
10.4.4 使用zipalign优化 407
10.5 小结 408
第四篇 进 阶 篇
第11章 Android特色功能开发 409
11.1 使用Google Map API 409
11.2 使用LBS功能 414
11.3 使用传感器 419
11.4 使用摄像头 422
11.5 多媒体开发 431
11.6 语音识别 439
11.7 小结 441
第12章 Android NDK开发 442
12.1 NDK开发基础 442
12.1.1 使用NDK的原因 442
12.1.2 使用NDK调用C或C++ 443
12.1.3 Android.mk和Application.mk 445
12.2 NDK开发入门 448
12.2.1 开发环境搭建 448
12.2.2 首个NDK项目 449
12.3 小结 456
第13章 Android游戏开发 457
13.1 手游开发基础 457
13.1.1 手游开发思路解析 457
13.1.2 贪食蛇和飞船游戏实例 462
13.1.3 认识Android游戏引擎 464
13.1.4 使用OpenGL和OpenGL ES 466
13.1.5 使用RenderScript 472
13.2 手游开发进阶 474
13.2.1 认识Cocos2d-x 475
13.2.2 架设Cocos2d-x开发环境 475
13.2.3 首个Cocos2d-x项目 475
13.2.4 认识Unity 3D 487
13.3 小结 489
附录A Hush Framework框架实例源码部署 490
附录B 微博应用实例源码部署 495
2.4.3 广播接收器(Broadcast Receiver) 23
2.4.4 内容提供者(Content Provider) 24
2.5 Android上下文 25
2.5.1 界面上下文(Activity Context) 25
2.5.2 应用上下文(Application Context) 27
2.6 Android数据存储 28
2.6.1 应用配置(Shared Preferences) 28
2.6.2 本地文件(Files) 29
2.6.3 数据库(SQLite) 30
2.7 Android应用界面 31
2.7.1 控件属性 31
2.7.2 布局(Layout) 33
2.7.3 事件(Event) 37
2.7.4 菜单(Menu) 39
2.7.5 主题(Theme) 41
2.7.6 对话框(Dialog) 42
2.8 Android图形界面 43
2.8.1 画笔(Paint) 43
2.8.2 画布(Canvas) 44
2.8.3 基础几何图形 46
2.8.4 常见图形变换 47
2.9 Android动画效果 50
2.9.1 逐帧动画(Frame Animation) 50
2.9.2 补间动画(Tween Animation) 51
2.10 Android开发环境 52
2.10.1 开发环境的搭建 53
2.10.2 首个Android项目 58
2.10.3 使用DDMS调试工具 63
2.11 小结 64
第3章 PHP开发准备 65
3.1 PHP开发基础 65
3.1.1 PHP语言简介 65
3.1.2 PHP语法简介 66
3.1.3 PHP开发起步 68
3.1.4 PHP面向对象编程 75
3.1.5 PHP的会话 78
3.2 PHP开发环境 80
3.2.1 开发环境的搭建 80
3.2.2 安装配置Xampp 81
3.2.3 管理Apache 83
3.2.4 管理MySQL 84
3.3 使用JSON通信 87
3.4 常用PHP开发框架 88
3.5 认识Smarty模板引擎 90
3.6 开发框架简介 93
3.6.1 框架的特点和优势 94
3.6.2 框架的基础目录结构 94
3.6.3 框架MVC思路讲解 97
3.6.4 框架MVC实例分析 99
3.7 小结 108
第二篇 实 战 篇
第4章 实例产品设计 109
4.1 为何选择微博 109
4.2 开发前的准备 110
4.2.1 选择开发模式 110
4.2.2 了解项目策划 111
4.2.3 了解原型设计 112
4.3 功能模块设计 112
4.4 应用界面设计 114
4.5 应用架构设计 115
4.6 通信协议定义 116
4.7 数据库结构设计 118
4.8 小结 120
第5章 程序架构设计 121
5.1 服务端程序架构设计 121
5.1.1 基础框架设计 122
5.1.2 调试框架设计 127
5.1.3 核心类库设计 130
5.1.4 服务端的MVC与SOA 136
5.2 客户端程序架构设计 136
5.2.1 基础框架设计 137
5.2.2 核心类包设计 138
5.2.3 Android应用的MVC 142
5.3 客户端界面架构设计 142
5.3.1 界面框架设计 143
5.3.2 主要界面设计 144
5.4 小结 146
第6章 服务端开发 147
6.1 开发入门 147
6.1.1 接口程序开发 147
6.1.2 调试框架开发 151
6.1.3 生成接口文档 155
6.2 验证接口 156
6.2.1 用户登录接口 156
6.2.2 用户登出接口 160
6.3 用户接口 162
6.3.1 新建用户接口 162
6.3.2 更新用户信息接口 164
6.3.3 查看用户信息接口 165
6.3.4 添加粉丝接口 167
6.3.5 删除粉丝接口 171
6.4 微博接口 172
6.4.1 发表微博接口 172
6.4.2 查看微博接口 174
6.4.3 微博列表接口 176
6.5 评论接口 180
6.5.1 发表评论接口 180
6.5.2 评论列表接口 182
6.6 图片接口 184
6.6.1 用户头像接口 185
6.6.2 头像列表接口 188
6.7 通知接口 189
6.8 Web版接口 192
6.8.1 Web版UI界面(jQuery Mobile) 193
6.8.2 Web版地图接口 195
6.9 小结 196
第7章 客户端开发 198
7.1 开发入门 198
7.1.1 开发思路梳理 198
7.1.2 掌握应用配置文件 200
7.1.3 常规程序开发与调试 211
7.2 界面布局和行为控制 221
7.2.1 使用Layout布局 221
7.2.2 使用Merge整合界面 224
7.2.3 使用Event控制用户行为 226
7.2.4 使用Intent控制界面切换 228
7.3 网络通信模块 231
7.3.1 使用HttpClient进行网络通信 231
7.3.2 支持CMWAP网络接入方式 237
7.3.3 使用JSON库为消息解码 240
7.3.4 使用Toast消息提示 245
7.4 异步任务模块 247
7.4.1 进程和线程 247
7.4.2 任务创建Thread 249
7.4.3 任务处理Handler 255
7.4.4 使用异步任务AsyncTask 257
7.5 全局功能模块 259
7.5.1 全局UI基类 259
7.5.2 全局Menu菜单 264
7.5.3 全局Dialog窗口 265
7.5.4 使用Service获取通知 266
7.5.5 使用Notification显示通知 270
7.6 用户登录界面 273
7.6.1 界面程序逻辑 273
7.6.2 使用TextView 273
7.6.3 使用EditText 274
7.6.4 使用Button 276
7.6.5 使用Shape和Selector 277
7.6.6 使用CheckBox 279
7.6.7 使用SharedPreference 280
7.7 微博列表界面 281
7.7.1 界面程序逻辑 281
7.7.2 使用ListView 286
7.7.3 使用ImageView 290
7.7.4 使用draw9patch 292
7.7.5 异步获取远程图片 294
7.7.6 使用SdCard缓存图片 297
7.7.7 使用SQLite缓存数据 300
7.8 我的微博列表 303
7.8.1 界面程序逻辑 303
7.8.2 使用ScrollView 309
7.8.3 使用自定义微博列表 310
7.9 微博文章界面 313
7.9.1 界面程序逻辑 313
7.9.2 界面布局进阶(综合使用UI控件) 319
7.9.3 发表评论功能实现 322
7.9.4 发表微博功能实现 326
7.10 用户配置界面 328
7.10.1 界面程序逻辑 328
7.10.2 使用自定义选项列表 333
7.10.3 修改签名功能实现 334
7.10.4 更换头像功能实现 334
7.11 网页界面开发 340
7.11.1 界面程序逻辑 340
7.11.2 使用WebView 341
7.11.3 使用ProgressDialog 343
7.11.4 使用WebView的重写和回调 346
7.11.5 网页地图实例分析 348
7.12 小结 349
第三篇 优 化 篇
第8章 性能分析 351
8.1 关于性能测试 351
8.1.1 服务端压力测试 352
8.1.2 客户端性能测试 359
8.2 瓶颈 364
8.2.1 服务端瓶颈分析 365
8.2.2 客户端瓶颈分析 366
8.3 优化的思路 366
8.4 小结 367
第9章 服务端优化 368
9.1 优化PHP程序 368
9.1.1 优化PHP代码 368
9.1.2 优化Session机制 371
9.1.3 使用缓存中间件 373
9.1.4 使用APC加速 376
9.2 优化数据传输 377
9.2.1 优化JSON协议 377
9.2.2 使用gzip压缩 379
9.3 其他优化 380
9.3.1 服务器优化 380
9.3.2 数据库优化 383
9.3.3 网络优化 386
9.4 小结 386
第10章 客户端优化 387
10.1 优化Android程序 387
10.1.1 优化Java代码 387
10.1.2 异步获取数据 391
10.1.3 文件资源缓存 391
10.1.4 数据库缓存 392
10.2 避免内存泄露 392
10.2.1 Android内存管理 392
10.2.2 如何判断内存泄露 393
10.2.3 常见内存泄露的处理 395
10.3 优化Android UI 396
10.3.1 模板代码优化 396
10.3.2 关于布局优化 397
10.3.3 使用Hierarchy Viewer工具 402
10.4 其他优化 403
10.4.1 优化图片 403
10.4.2 优化APK包 403
10.4.3 使用keytool和jarsigner签名 404
10.4.4 使用zipalign优化 407
10.5 小结 408
第四篇 进 阶 篇
第11章 Android特色功能开发 409
11.1 使用Google Map API 409
11.2 使用LBS功能 414
11.3 使用传感器 419
11.4 使用摄像头 422
11.5 多媒体开发 431
11.6 语音识别 439
11.7 小结 441
第12章 Android NDK开发 442
12.1 NDK开发基础 442
12.1.1 使用NDK的原因 442
12.1.2 使用NDK调用C或C++ 443
12.1.3 Android.mk和Application.mk 445
12.2 NDK开发入门 448
12.2.1 开发环境搭建 448
12.2.2 首个NDK项目 449
12.3 小结 456
第13章 Android游戏开发 457
13.1 手游开发基础 457
13.1.1 手游开发思路解析 457
13.1.2 贪食蛇和飞船游戏实例 462
13.1.3 认识Android游戏引擎 464
13.1.4 使用OpenGL和OpenGL ES 466
13.1.5 使用RenderScript 472
13.2 手游开发进阶 474
13.2.1 认识Cocos2d-x 475
13.2.2 架设Cocos2d-x开发环境 475
13.2.3 首个Cocos2d-x项目 475
13.2.4 认识Unity 3D 487
13.3 小结 489
附录A Hush Framework框架实例源码部署 490
附录B 微博应用实例源码部署 495
前言
2012年,移动互联网革命正在如火如荼地进行,一个充满机遇的巨大市场正在开启,无论是创业者还是从业者都需要做好准备。Android和PHP两种技术,作为目前移动领域和互联网领域中的热门技术,已经受到广大开发者们的关注。本书是目前市面上唯一一本同时讲述Android客户端开发和PHP服务端开发两方面内容,并且把Android移动互联网应用开发的完整解决方案分析透彻的书籍。通过本书,您不仅可以学习到Android客户端开发技巧,而且可以掌握PHP服务端开发的精华,甚至还可以开拓软件架构的思路。阅读了本书,您就真正找到了一条精通“Android客户端和PHP服务端开发”的捷径!
本书的写作风格大众化,注重实用性,章节精心编排,讲解由浅入深,力求让读者能够在最快的时间内上手,同时也可以拓宽读者在移动互联网应用开发方面的思路。特别要指出的是,本书的代码实例都源自真实的项目,实用价值极高。此外,书中很多内容都融合了笔者多年来在互联网软件架构方面的经验。总而言之,本书绝对是一本不可多得的经典之作!
如何使用本书
在开始阅读本书之前,请您先阅读以下内容,以保证能最快地了解本书的思路和结构,并快速地找到最适合自己的阅读方式。考虑到实用性,也为了让思路更清晰,本书独创性地采用了“项目跟进式”的结构,以具有代表性的“微博应用”实例项目为主线,贯穿始终。全书内容分为四大部分:准备篇、实战篇、优化篇、进阶篇,简介如下。
准备篇:本篇主要介绍Android和PHP开发中需要用到的基础概念与用法,为后面的“实战篇”做准备。不管做什么事情,打好基础是至关重要的,所以笔者建议大家好好阅读本篇内容。
实战篇:在本篇中,我们将带领您逐步完成一个完整的“微博应用”项目,从前期的产品设计、架构设计,到服务端和客户端的编码,直至最后的大功告成,整个过程一气呵成,让读者感觉仿佛亲身参与到这个项目中,以达到最好的学习效果。
优化篇:系统优化已经成为当代软件开发过程中至关重要的一个环节。在本篇中,读者将学到一些从实际项目中总结出的非常实用的优化经验和技巧;如果您想更深入地学习使用Android平台和PHP语言,绝不能错过本篇。
进阶篇:本篇包含一些Android开发中的进阶内容,主要包括Android NDK和Android游戏开发相关的入门知识。此外,本篇内容还涉及OpenGL、RenderScript相关的高级用法,以及包括Cocos2d-x和Unity 3D在内的主流游戏引擎的相关知识,适合希望进一步学习的读者阅读。
本书共13章,每章的主要内容见下面的“章节简介”,方便读者快速查找感兴趣的部分。
章节简介
第1章学前必读
本章的主要目的是让读者对移动互联网应用开发有一个比较清晰的认识,同时讲清楚选择Android加PHP这套解决方案的原因,并向读者介绍在学习过程中所要使用的正确的学习方法和思路。
第2章Android开发准备
本章内容包含了对Android系统框架、Android应用程序框架、Android图形界面系统以及Android常见开发思路的介绍。另外,通过本章的学习,读者还将学会如何安装和使用Android的开发环境和必备工具(Eclipse和ADT),并学会创建自己的第一个Android项目(Hello World项目),由此开始您的Android开发之旅。
第3章PHP开发准备
通过本章的学习,您将快速地学会如何使用PHP进行服务端开发,如果您已经有一定的服务端开发基础,学习起来会更加轻松。当然,本章也包括PHP开发环境(Xampp)的架设和一些其他配套服务端组件(Apache和MySQL)的基础管理。最后,本章还重点介绍了一个基于Zend Framework和Smarty的PHP开发框架:Hush Framework,本书实例的服务端正是采用这个框架进行开发的。
第4章实例产品设计
从这一章开始,我们将动手完成一个完整的移动互联网项目,即“微博应用”实例的项目。本章所讲的主要是项目的前期工作,包括功能模块设计以及一些项目策划的内容。当然,如果您是项目管理人员,可能会比开发者们对本章更感兴趣,里面所涉及的一些设计方法和思路,均是很实用的经验。
第5章程序架构设计
本章应该算是本书的核心章节之一,这里我们将对“微博应用”项目实例的服务端以及客户端的整体代码框架进行深入的剖析。由于架构设计是整个项目的基础,所以如果您要继续往下学习,就必须把这里的思路都理清楚。如果您善于思考,应该能从本章学习到不少Android和PHP应用架构的精髓。
本书的写作风格大众化,注重实用性,章节精心编排,讲解由浅入深,力求让读者能够在最快的时间内上手,同时也可以拓宽读者在移动互联网应用开发方面的思路。特别要指出的是,本书的代码实例都源自真实的项目,实用价值极高。此外,书中很多内容都融合了笔者多年来在互联网软件架构方面的经验。总而言之,本书绝对是一本不可多得的经典之作!
如何使用本书
在开始阅读本书之前,请您先阅读以下内容,以保证能最快地了解本书的思路和结构,并快速地找到最适合自己的阅读方式。考虑到实用性,也为了让思路更清晰,本书独创性地采用了“项目跟进式”的结构,以具有代表性的“微博应用”实例项目为主线,贯穿始终。全书内容分为四大部分:准备篇、实战篇、优化篇、进阶篇,简介如下。
准备篇:本篇主要介绍Android和PHP开发中需要用到的基础概念与用法,为后面的“实战篇”做准备。不管做什么事情,打好基础是至关重要的,所以笔者建议大家好好阅读本篇内容。
实战篇:在本篇中,我们将带领您逐步完成一个完整的“微博应用”项目,从前期的产品设计、架构设计,到服务端和客户端的编码,直至最后的大功告成,整个过程一气呵成,让读者感觉仿佛亲身参与到这个项目中,以达到最好的学习效果。
优化篇:系统优化已经成为当代软件开发过程中至关重要的一个环节。在本篇中,读者将学到一些从实际项目中总结出的非常实用的优化经验和技巧;如果您想更深入地学习使用Android平台和PHP语言,绝不能错过本篇。
进阶篇:本篇包含一些Android开发中的进阶内容,主要包括Android NDK和Android游戏开发相关的入门知识。此外,本篇内容还涉及OpenGL、RenderScript相关的高级用法,以及包括Cocos2d-x和Unity 3D在内的主流游戏引擎的相关知识,适合希望进一步学习的读者阅读。
本书共13章,每章的主要内容见下面的“章节简介”,方便读者快速查找感兴趣的部分。
章节简介
第1章学前必读
本章的主要目的是让读者对移动互联网应用开发有一个比较清晰的认识,同时讲清楚选择Android加PHP这套解决方案的原因,并向读者介绍在学习过程中所要使用的正确的学习方法和思路。
第2章Android开发准备
本章内容包含了对Android系统框架、Android应用程序框架、Android图形界面系统以及Android常见开发思路的介绍。另外,通过本章的学习,读者还将学会如何安装和使用Android的开发环境和必备工具(Eclipse和ADT),并学会创建自己的第一个Android项目(Hello World项目),由此开始您的Android开发之旅。
第3章PHP开发准备
通过本章的学习,您将快速地学会如何使用PHP进行服务端开发,如果您已经有一定的服务端开发基础,学习起来会更加轻松。当然,本章也包括PHP开发环境(Xampp)的架设和一些其他配套服务端组件(Apache和MySQL)的基础管理。最后,本章还重点介绍了一个基于Zend Framework和Smarty的PHP开发框架:Hush Framework,本书实例的服务端正是采用这个框架进行开发的。
第4章实例产品设计
从这一章开始,我们将动手完成一个完整的移动互联网项目,即“微博应用”实例的项目。本章所讲的主要是项目的前期工作,包括功能模块设计以及一些项目策划的内容。当然,如果您是项目管理人员,可能会比开发者们对本章更感兴趣,里面所涉及的一些设计方法和思路,均是很实用的经验。
第5章程序架构设计
本章应该算是本书的核心章节之一,这里我们将对“微博应用”项目实例的服务端以及客户端的整体代码框架进行深入的剖析。由于架构设计是整个项目的基础,所以如果您要继续往下学习,就必须把这里的思路都理清楚。如果您善于思考,应该能从本章学习到不少Android和PHP应用架构的精髓。
. 第6章服务端开发
本章也是本书的重点章节之一,这里我们将在第5章的服务端架构基础上展开,分析和讲解实例服务端的代码逻辑和写法,带领您进一步深入认识PHP服务端开发的方法。读者可以将本章的部分章节内容和第7章的部分章节内容进行对照阅读,这样对理解移动互联网应用的开发思路会很有帮助。
第7章客户端开发
本章也是本书的重点章节之一,在这里,您可以接触到几乎所有在Android应用开发中经常用到的控件和模块。通过对本章的学习,读者将不仅学会如何使用它们,而且学会如何正确地在项目中使用它们,这两者之间是完全不同的两个境界!而这也正是本书最宝贵、最特别的地方,希望大家能好好阅读和体会。
第8章性能分析
有过项目实战经验的朋友应该都知道,其实在编码阶段完成之后,项目最多也才进行了一半,后面还有很多的事情需要我们来做,而性能测试和优化就是其中非常重要的一个环节,本章将对相关内容进行详细的介绍。另外,在本章中,读者也可以学到一些非常实用的优化思路和经验。
第9章服务端优化
根据第8章中总结的优化思路,本章将教会读者如何对PHP服务端的各个组成部分实施优化策略,着重介绍了PHP代码优化、JSON协议优化,以及HTTP服务器和MySQL数据库优化相关的内容,相信这些经验在深入学习PHP服务端开发的过程中会起到非常大的作用。
第10章客户端优化
在本章中,您将学到许多有用的Android开发中的优化思路和方法。本章重点介绍了Android程序优化、Android UI优化、图片优化,以及与避免内存泄露相关的内容,这些经验对能否写出一个高质量的Android应用来说是非常重要的。
第11章Android特色功能开发
本章主要介绍一些与Android系统提供的特色功能开发相关的知识,比如Google Map API的使用、LBS相关功能、传感器的使用、摄像头的使用,以及语音识别功能等。相信掌握了这些知识后,我们可以开发出许多别具特色的Android应用。
第12章Android NDK开发
本章介绍了与Android NDK开发相关的基础知识,并创建首个NDK项目。如果您需要使用C或C++语言来开发Android程序,或者想把一些基于C或C++的程序或者类库移植到Android平台下,那么肯定会对本章内容比较感兴趣。
第13章Android游戏开发
本章介绍了与Android游戏开发相关的基础知识,包含了OpenGL和RenderScript的基础用法,以及Cocos2d-x和Unity 3D游戏引擎的相关内容。游戏开发和应用开发的思路还是有很大区别的,如果您对Android游戏开发比较感兴趣,请关注本章内容,相信本章知识对Android游戏开发的学习会有很大帮助。
由于时间有限,书中难免存有疏漏,诚恳希望各位读者批评、指正。当然,如果你在阅读过程中发现了问题,或者遇到疑问,欢迎到作者的技术博客上留言和交流,博客地址为http://blog.csdn.net/shagoo,希望和大家一起共同进步。
源码简介
请读者登录华章网站(www.hzbook.com)的本书页面下载本书所有源码。高质量的应用实例是本书的一大特色,所有的实例代码都按照实际项目的规范来书写,且都经过严格的审核,保证运行无误。另外,本书实例源码的获取也采用了最接近实际项目开发的形式,有经验的读者甚至可以直接通过SVN工具从Google Code项目SVN源中获取。本书主要实例源码有以下几个。
1. Hush Framework实例源码
Hush Framework是本书重点介绍的PHP开源开发框架,该框架的核心类库和实例源码都可以从Google Code上的hush-framework项目主页的Downloads页面中直接下载,项目地址为http://code.google.com/p/hush-framework/。另外,与Hush Framework实例部署有关的内容请参考本书附录A。
2. 微博实例源码
微博实例源码中包含了两个项目,即服务端PHP项目(app-demos-server)和客户端Android项目(app-demos-client),其源码包“android-php-weibo.zip”可以从Google Code上android-php项目的下载页面中直接下载,地址为http://code.google.com/p/android-php/。另外,与微博实例部署有关的信息请参考本书附录B。
3. 特色功能源码
该实例项目包含了第11章“Android特色功能开发”中涉及的所有实例的源码,包含了Google Map API使用、传感器使用以及摄像头使用等实例,其源码包“android-php-special.zip”也可以从Google Code上的android-php项目主页的Downloads页面中直接下载。
4. OpenGL实例源码
该实例项目包含了第13章“Android游戏开发”中涉及的与OpenGL使用有关的实例源码,其中包括与2D和3D渲染有关的两个实例,其源码包“android-php-opengl.zip”也可以从Google Code上的android-php项目主页的Downloads页面中直接下载。
另外,以上所有实例项目的源码都可以通过Eclipse的Import工具(即File菜单中的Import选项)导入Eclipse开发工具中进行阅读。成功导入之后的项目代码树如下图所示。
此外,还有一些实例源码属于第三方的开发包(SDK),比如Android NDK中的hello-jni项目、Cocos2d-x开发包中的Hello World项目等。
致谢
首先,感谢华章公司的编辑们,没有你们的建议和帮助,绝对无法制作出如此经典的技术书籍;其次,感谢我的妻子和刚出世的宝宝,你们为我的创作提供了无穷的动力;再次,还要感谢我的父母和亲友,你们的支持和鼓励让我更有信心;最后,我必须向Android和PHP技术的创造者们致敬,你们创造出了如此优秀的产品,为我们开启了移动互联网的精彩世界。
本章也是本书的重点章节之一,这里我们将在第5章的服务端架构基础上展开,分析和讲解实例服务端的代码逻辑和写法,带领您进一步深入认识PHP服务端开发的方法。读者可以将本章的部分章节内容和第7章的部分章节内容进行对照阅读,这样对理解移动互联网应用的开发思路会很有帮助。
第7章客户端开发
本章也是本书的重点章节之一,在这里,您可以接触到几乎所有在Android应用开发中经常用到的控件和模块。通过对本章的学习,读者将不仅学会如何使用它们,而且学会如何正确地在项目中使用它们,这两者之间是完全不同的两个境界!而这也正是本书最宝贵、最特别的地方,希望大家能好好阅读和体会。
第8章性能分析
有过项目实战经验的朋友应该都知道,其实在编码阶段完成之后,项目最多也才进行了一半,后面还有很多的事情需要我们来做,而性能测试和优化就是其中非常重要的一个环节,本章将对相关内容进行详细的介绍。另外,在本章中,读者也可以学到一些非常实用的优化思路和经验。
第9章服务端优化
根据第8章中总结的优化思路,本章将教会读者如何对PHP服务端的各个组成部分实施优化策略,着重介绍了PHP代码优化、JSON协议优化,以及HTTP服务器和MySQL数据库优化相关的内容,相信这些经验在深入学习PHP服务端开发的过程中会起到非常大的作用。
第10章客户端优化
在本章中,您将学到许多有用的Android开发中的优化思路和方法。本章重点介绍了Android程序优化、Android UI优化、图片优化,以及与避免内存泄露相关的内容,这些经验对能否写出一个高质量的Android应用来说是非常重要的。
第11章Android特色功能开发
本章主要介绍一些与Android系统提供的特色功能开发相关的知识,比如Google Map API的使用、LBS相关功能、传感器的使用、摄像头的使用,以及语音识别功能等。相信掌握了这些知识后,我们可以开发出许多别具特色的Android应用。
第12章Android NDK开发
本章介绍了与Android NDK开发相关的基础知识,并创建首个NDK项目。如果您需要使用C或C++语言来开发Android程序,或者想把一些基于C或C++的程序或者类库移植到Android平台下,那么肯定会对本章内容比较感兴趣。
第13章Android游戏开发
本章介绍了与Android游戏开发相关的基础知识,包含了OpenGL和RenderScript的基础用法,以及Cocos2d-x和Unity 3D游戏引擎的相关内容。游戏开发和应用开发的思路还是有很大区别的,如果您对Android游戏开发比较感兴趣,请关注本章内容,相信本章知识对Android游戏开发的学习会有很大帮助。
由于时间有限,书中难免存有疏漏,诚恳希望各位读者批评、指正。当然,如果你在阅读过程中发现了问题,或者遇到疑问,欢迎到作者的技术博客上留言和交流,博客地址为http://blog.csdn.net/shagoo,希望和大家一起共同进步。
源码简介
请读者登录华章网站(www.hzbook.com)的本书页面下载本书所有源码。高质量的应用实例是本书的一大特色,所有的实例代码都按照实际项目的规范来书写,且都经过严格的审核,保证运行无误。另外,本书实例源码的获取也采用了最接近实际项目开发的形式,有经验的读者甚至可以直接通过SVN工具从Google Code项目SVN源中获取。本书主要实例源码有以下几个。
1. Hush Framework实例源码
Hush Framework是本书重点介绍的PHP开源开发框架,该框架的核心类库和实例源码都可以从Google Code上的hush-framework项目主页的Downloads页面中直接下载,项目地址为http://code.google.com/p/hush-framework/。另外,与Hush Framework实例部署有关的内容请参考本书附录A。
2. 微博实例源码
微博实例源码中包含了两个项目,即服务端PHP项目(app-demos-server)和客户端Android项目(app-demos-client),其源码包“android-php-weibo.zip”可以从Google Code上android-php项目的下载页面中直接下载,地址为http://code.google.com/p/android-php/。另外,与微博实例部署有关的信息请参考本书附录B。
3. 特色功能源码
该实例项目包含了第11章“Android特色功能开发”中涉及的所有实例的源码,包含了Google Map API使用、传感器使用以及摄像头使用等实例,其源码包“android-php-special.zip”也可以从Google Code上的android-php项目主页的Downloads页面中直接下载。
4. OpenGL实例源码
该实例项目包含了第13章“Android游戏开发”中涉及的与OpenGL使用有关的实例源码,其中包括与2D和3D渲染有关的两个实例,其源码包“android-php-opengl.zip”也可以从Google Code上的android-php项目主页的Downloads页面中直接下载。
另外,以上所有实例项目的源码都可以通过Eclipse的Import工具(即File菜单中的Import选项)导入Eclipse开发工具中进行阅读。成功导入之后的项目代码树如下图所示。
此外,还有一些实例源码属于第三方的开发包(SDK),比如Android NDK中的hello-jni项目、Cocos2d-x开发包中的Hello World项目等。
致谢
首先,感谢华章公司的编辑们,没有你们的建议和帮助,绝对无法制作出如此经典的技术书籍;其次,感谢我的妻子和刚出世的宝宝,你们为我的创作提供了无穷的动力;再次,还要感谢我的父母和亲友,你们的支持和鼓励让我更有信心;最后,我必须向Android和PHP技术的创造者们致敬,你们创造出了如此优秀的产品,为我们开启了移动互联网的精彩世界。
媒体评论
本书独辟蹊径,把Android客户端技术和PHP服务端技术结合起来进行讲解,不仅很好地介绍了这两个技术各自的特点和用法,也为开发者们提供了一些相当不错的Android应用的解决方案。
—— 51CTO社区
这绝不是一本普通的技术书籍,同时讲解两种完全不同的技术,却丝毫没有影响内容的质量。本书对PHP开发部分的讲解深入浅出、精准到位,读后让人受益良多;其实例源码架构清晰、实用性强,具有极高的参考价值。值得收藏,强烈推荐!
—— PHPChina社区
本书的内容相当丰富,全面介绍了Android平台和PHP语言开发的方方面面,可作为技术人员的开发参考手册。本书的结构十分清晰,包括:理论、实战、优化、进阶四个部分,内容由浅入深,讲解深入透彻,能帮助读者梳理知识、开拓思路。本书的实例非常实用,读者可以从大量精彩的实例代码中获得丰富的实战经验。拥有本书,不仅能让你快速掌握Android客户端和PHP服务端的开发技术,还能让你深入了解Android网络应用的优化技巧。对于广大Android开发者来说,这*是一本不可不读的经典著作!
本书独辟蹊径,把Android客户端技术和PHP服务端技术结合起来进行讲解,不仅很好地介绍了这两个技术各自的特点和用法,也为开发者们提供了一些相当不错的Android应用的解决方案。
—— 51CTO社区
这绝不是一本普通的技术书籍,同时讲解两种完全不同的技术,却丝毫没有影响内容的质量。本书对PHP开发部分的讲解深入浅出、精准到位,读后让人受益良多;其实例源码架构清晰、实用性强,具有极高的参考价值。值得收藏,强烈推荐!
—— PHPChina社区
本书的内容相当丰富,全面介绍了Android平台和PHP语言开发的方方面面,可作为技术人员的开发参考手册。本书的结构十分清晰,包括:理论、实战、优化、进阶四个部分,内容由浅入深,讲解深入透彻,能帮助读者梳理知识、开拓思路。本书的实例非常实用,读者可以从大量精彩的实例代码中获得丰富的实战经验。拥有本书,不仅能让你快速掌握Android客户端和PHP服务端的开发技术,还能让你深入了解Android网络应用的优化技巧。对于广大Android开发者来说,这*是一本不可不读的经典著作!
—— 杨远(极游网CTO)
—— 51CTO社区
这绝不是一本普通的技术书籍,同时讲解两种完全不同的技术,却丝毫没有影响内容的质量。本书对PHP开发部分的讲解深入浅出、精准到位,读后让人受益良多;其实例源码架构清晰、实用性强,具有极高的参考价值。值得收藏,强烈推荐!
—— PHPChina社区
本书的内容相当丰富,全面介绍了Android平台和PHP语言开发的方方面面,可作为技术人员的开发参考手册。本书的结构十分清晰,包括:理论、实战、优化、进阶四个部分,内容由浅入深,讲解深入透彻,能帮助读者梳理知识、开拓思路。本书的实例非常实用,读者可以从大量精彩的实例代码中获得丰富的实战经验。拥有本书,不仅能让你快速掌握Android客户端和PHP服务端的开发技术,还能让你深入了解Android网络应用的优化技巧。对于广大Android开发者来说,这*是一本不可不读的经典著作!
本书独辟蹊径,把Android客户端技术和PHP服务端技术结合起来进行讲解,不仅很好地介绍了这两个技术各自的特点和用法,也为开发者们提供了一些相当不错的Android应用的解决方案。
—— 51CTO社区
这绝不是一本普通的技术书籍,同时讲解两种完全不同的技术,却丝毫没有影响内容的质量。本书对PHP开发部分的讲解深入浅出、精准到位,读后让人受益良多;其实例源码架构清晰、实用性强,具有极高的参考价值。值得收藏,强烈推荐!
—— PHPChina社区
本书的内容相当丰富,全面介绍了Android平台和PHP语言开发的方方面面,可作为技术人员的开发参考手册。本书的结构十分清晰,包括:理论、实战、优化、进阶四个部分,内容由浅入深,讲解深入透彻,能帮助读者梳理知识、开拓思路。本书的实例非常实用,读者可以从大量精彩的实例代码中获得丰富的实战经验。拥有本书,不仅能让你快速掌握Android客户端和PHP服务端的开发技术,还能让你深入了解Android网络应用的优化技巧。对于广大Android开发者来说,这*是一本不可不读的经典著作!
—— 杨远(极游网CTO)
书摘
第一篇
准备篇
第1章学 前 必 读
在学习任何知识之前,做好准备工作是非常有必要的。在本章,我们先来了解一下目前正如火如荼的移动互联网时代的大背景,然后我们会讲清楚我们为何要学习Android和PHP这套组合方案,以及学习Android和PHP开发的大体思路和学习方法。相信大家读完本章以后,不仅会对Android和PHP这个强大的组合更感兴趣,而且之后的学习之路会更加顺畅。
1.1移动互联网时代的来临
2011年,Android操作系统就已经占领了全球智能手机市场份额的半壁江山,霸主的地位彰显无遗(如图1-1所示)。在国内,随着各大手机厂商的更新换代,Android操作系统的占有率也在火速上升中;中国移动公司已经早早推出了自己基于Android的OMS系统和OPhone,甚至连各大互联网巨头也在纷纷推出自己基于Android的手机产品。据统计,2012年内全球智能手机市场增长率达到49%,中国移动互联网用户量已经突破4亿,手机用户量也已经超越了PC。
看到这里,相信触觉敏感的你应该已经感觉到这个巨大市场里面无限的潜力了吧!我们来分析一下。首先,以目前移动互联网发展的迅猛势头,在可以预见的不久将来,全球移动终端将全面升级到智能系统,而以Android操作系统在其中占的比例来看,必将会瓜分到这块“大蛋糕”的很大一部分。其次,随着移动互联网市场的不断膨胀,对移动终端开发人员的需求量将会飞速增加,对于我们开发者来说,这是个绝好的提升自己的机会,试问,我们怎能放过?不要犹豫了,让我们一起加入到Android平台应用开发的大潮里来吧!
1.2为何选择Android和PHP
时至今日,Android和PHP已经发展成为移动领域和互联网领域最领先的技术方案之一。那么,我们为何要选择Android和PHP这套解决方案呢?原因已经很明显。此外,我们还可以参考这两种技术的市场占有率。前面我们已经提到过Android系统的全球占有率,然而,目前PHP语言在互联网领域的使用率甚至比Android系统更高,所以,Android系统加上PHP语言如此强大的组合,我们又怎能忽视呢?接下来,让我们分析一下Android系统和PHP语言各自的优势所在。
1. Android平台的优势
开放性:毫无疑问,Android平台的开放性就是它在短时间内能占领市场的最强武器之一。Google希望通过Android平台打通运营商、设备制造商、开发商以及其他各个层面,建立起标准化、开放式的移动平台生态系统。
完备性:对于开发商或者开发者来说,系统平台的完备性无疑是他决定是否加入这个阵营最重要的因素之一。而Android系统无疑是目前功能最为强大,设计最为精良的移动操作系统之一,而且背后还有Google公司的强大实力作为支持,这也大大减少了项目开发的后顾之忧。
创造性:由于Android系统是开源的,允许第三方修改。对于开发商来说,在这个平台之上,可以把自己的创造力发挥到最大;而对于设备制造商来说,根据自己的硬件进行调优,从而能够更好地适应硬件,与之形成良好的结合。
2. PHP语言的优势
稳定性:毫无疑问,PHP已经是目前互联网服务端使用最广泛的编程语言之一,目前PHP在互联网应用领域的占有率位居全球第一。试问,如果本身不够成熟和稳定,如何能占有如此大的市场呢?
易用性:简单实用,学习成本低,这也是很多开发者愿意选择PHP的最重要原因,特别是对于互联网项目来说,需求变动是非常大的,因此,如果选择PHP,就可以节省出更多时间和精力去做其他的事情。
开放性:PHP本身是开源的,允许开发者对其进行扩展和优化,其整套服务端部署解决方案也是免费的,因此,使用这套解决方案能大大地降低成本,对于大部分资金紧张的互联网企业来说,何乐而不为呢?
完备性:LAMP(Linux+Apache+MySQL+PHP)这个绝佳组合早已闻名业界,而现在Nginx+PHP FastCGI的出现使其HTTP服务端的性能更上一层楼。对于目前绝大部分互联网应用来说,这套解决方案都可以很好地满足它们的需求。
事实上,目前已经有很多成功的移动互联网应用软件和游戏正在使用Android加PHP的架构,其中就包括风头正劲的“新浪微博”和“腾讯微博”。这些成功的例子很好地验证了Android加PHP这个组合的强大。当然,我们的开发团队在许多的实际项目中也都使用这套架构来进行开发。Android加PHP所展现出的灵活度和扩展性也确实让我们相当满意。
总而言之,Android的创造性加上PHP的灵活性确实是“天造之和”,也可以满足绝大部分的移动互联网应用快速变化的需求。当然,如果我们希望在服务端采用其他的技术,例如Java、Python或者Ruby On Rails,这也是没有问题的。因为我们的服务端用于和客户端打交道的实际上是JSON协议,而JSON是一种跨语言的协议,我们在服务端可以用任意语言来组合JSON数据并供给Android客户端使用。关于JSON协议的内容我们会在本书3.3节中详细介绍。
准备篇
第1章学 前 必 读
在学习任何知识之前,做好准备工作是非常有必要的。在本章,我们先来了解一下目前正如火如荼的移动互联网时代的大背景,然后我们会讲清楚我们为何要学习Android和PHP这套组合方案,以及学习Android和PHP开发的大体思路和学习方法。相信大家读完本章以后,不仅会对Android和PHP这个强大的组合更感兴趣,而且之后的学习之路会更加顺畅。
1.1移动互联网时代的来临
2011年,Android操作系统就已经占领了全球智能手机市场份额的半壁江山,霸主的地位彰显无遗(如图1-1所示)。在国内,随着各大手机厂商的更新换代,Android操作系统的占有率也在火速上升中;中国移动公司已经早早推出了自己基于Android的OMS系统和OPhone,甚至连各大互联网巨头也在纷纷推出自己基于Android的手机产品。据统计,2012年内全球智能手机市场增长率达到49%,中国移动互联网用户量已经突破4亿,手机用户量也已经超越了PC。
看到这里,相信触觉敏感的你应该已经感觉到这个巨大市场里面无限的潜力了吧!我们来分析一下。首先,以目前移动互联网发展的迅猛势头,在可以预见的不久将来,全球移动终端将全面升级到智能系统,而以Android操作系统在其中占的比例来看,必将会瓜分到这块“大蛋糕”的很大一部分。其次,随着移动互联网市场的不断膨胀,对移动终端开发人员的需求量将会飞速增加,对于我们开发者来说,这是个绝好的提升自己的机会,试问,我们怎能放过?不要犹豫了,让我们一起加入到Android平台应用开发的大潮里来吧!
1.2为何选择Android和PHP
时至今日,Android和PHP已经发展成为移动领域和互联网领域最领先的技术方案之一。那么,我们为何要选择Android和PHP这套解决方案呢?原因已经很明显。此外,我们还可以参考这两种技术的市场占有率。前面我们已经提到过Android系统的全球占有率,然而,目前PHP语言在互联网领域的使用率甚至比Android系统更高,所以,Android系统加上PHP语言如此强大的组合,我们又怎能忽视呢?接下来,让我们分析一下Android系统和PHP语言各自的优势所在。
1. Android平台的优势
开放性:毫无疑问,Android平台的开放性就是它在短时间内能占领市场的最强武器之一。Google希望通过Android平台打通运营商、设备制造商、开发商以及其他各个层面,建立起标准化、开放式的移动平台生态系统。
完备性:对于开发商或者开发者来说,系统平台的完备性无疑是他决定是否加入这个阵营最重要的因素之一。而Android系统无疑是目前功能最为强大,设计最为精良的移动操作系统之一,而且背后还有Google公司的强大实力作为支持,这也大大减少了项目开发的后顾之忧。
创造性:由于Android系统是开源的,允许第三方修改。对于开发商来说,在这个平台之上,可以把自己的创造力发挥到最大;而对于设备制造商来说,根据自己的硬件进行调优,从而能够更好地适应硬件,与之形成良好的结合。
2. PHP语言的优势
稳定性:毫无疑问,PHP已经是目前互联网服务端使用最广泛的编程语言之一,目前PHP在互联网应用领域的占有率位居全球第一。试问,如果本身不够成熟和稳定,如何能占有如此大的市场呢?
易用性:简单实用,学习成本低,这也是很多开发者愿意选择PHP的最重要原因,特别是对于互联网项目来说,需求变动是非常大的,因此,如果选择PHP,就可以节省出更多时间和精力去做其他的事情。
开放性:PHP本身是开源的,允许开发者对其进行扩展和优化,其整套服务端部署解决方案也是免费的,因此,使用这套解决方案能大大地降低成本,对于大部分资金紧张的互联网企业来说,何乐而不为呢?
完备性:LAMP(Linux+Apache+MySQL+PHP)这个绝佳组合早已闻名业界,而现在Nginx+PHP FastCGI的出现使其HTTP服务端的性能更上一层楼。对于目前绝大部分互联网应用来说,这套解决方案都可以很好地满足它们的需求。
事实上,目前已经有很多成功的移动互联网应用软件和游戏正在使用Android加PHP的架构,其中就包括风头正劲的“新浪微博”和“腾讯微博”。这些成功的例子很好地验证了Android加PHP这个组合的强大。当然,我们的开发团队在许多的实际项目中也都使用这套架构来进行开发。Android加PHP所展现出的灵活度和扩展性也确实让我们相当满意。
总而言之,Android的创造性加上PHP的灵活性确实是“天造之和”,也可以满足绝大部分的移动互联网应用快速变化的需求。当然,如果我们希望在服务端采用其他的技术,例如Java、Python或者Ruby On Rails,这也是没有问题的。因为我们的服务端用于和客户端打交道的实际上是JSON协议,而JSON是一种跨语言的协议,我们在服务端可以用任意语言来组合JSON数据并供给Android客户端使用。关于JSON协议的内容我们会在本书3.3节中详细介绍。
. 1.3如何学习Android和PHP
前面我们已经讨论过“为何学”的问题,大家应该对Android加PHP这套应用开发解决方案有了大致的了解。接下来介绍“如何学”的问题,由于本书的内容比较广泛,既涉及客户端开发的技术也包含很多服务端开发的内容,所以在正式开始学习本书之前,先搞清楚应该使用什么样的学习方法比较有效是非常有必要的。接下来,笔者会把这个问题分解为以下几个部分来探讨。
1.3.1如何学习Android
由于Android学习是本书最核心的内容,因此我们先来分析。由于Android应用框架是基于Java语言的,所以在学习Android之前,最理想的状态是您已经具有一定的Java语言编程基础,对Java语言的常用语法和常用类包(package)的使用也有一定的认识。当然,即使您是一名Java初学者,同样也可以从本书中学到一些非常有用的Java编程的经验。以下是Android SDK中包含的一些比较重要的Java基础类包,建议大家先自行熟悉起来。
表1-1Android SDK中的重要Java基础类包
Java类包名作用
java.ioJava普通I/O包
java.nioJava异步I/O包
java.langJava基础语言包
java.mathJava算数类包(提供高精度计算)
java.netJava基础网络接口包(URI/URL)
java.textJava文本通用接口包(DateFormat/NumberFormat)
java.utilJava常用辅助工具包(ArrayList/HashMap)
javax.cryptoJava基础加解密工具包(Cipher)
javax.xmlJava的Xml工具类包(SAXParser)
org.apache.httpJava的Http网络工具包(HttpClient)
org.jsonJava的Json工具类包(JSONObject/JSONArray)
当然,在Android SDK中除了以上这些Java基础包之外,更多的还是Android系统本身的功能类包。当然,如果要查阅更多关于Android类包的说明文档,就需要参考Android的SDK文档了。我们可以在浏览器中打开Android的SDK里的docs/reference/packages.html网页进行查阅。想要把这里面的类包全部弄懂,必将是一个漫长而艰苦的过程。当然,假如坚持到了那一天,我相信你也已经成为Android大师了。
结合本书来讲,如果你没有任何的Java编程经验或者Android基础,那么一定要更加认真地阅读本书第2章的内容,此章不仅对Android系统框架和应用框架进行了精辟的讲解,而且结合实例让你快速熟悉Android的开发框架。接下去,在阅读完本书“实战篇”的内容并慢慢熟悉Android开发之后,还要注意学习和理解“优化篇”中关于系统优化的技巧,因为没有经过优化的系统是非常脆弱的。只有在把本书“实战篇”和“优化篇”的内容全部理解透彻之后,才能往下学习“进阶篇”的内容。总而言之,学习Android开发一定要坚持“稳扎稳打,层层递进”的学习原则,这样才能达到最佳的学习效果。
1.3.2如何学习PHP
可能很多人会认为PHP学起来比较简单,事实也确实如此,但是这并不意味着我们可以很轻易地掌握使用PHP进行服务端开发的技巧。由于服务端编程涉及的知识面比较广,除了编程语言本身,还需要和很多的服务端组件打交道,比如HTTP服务器、缓存中间件、数据库等,所以我们也需要做好“刻苦学习”的准备。
如果你没有任何PHP开发基础,请认真阅读本书第3章,因为该章能够让你快速地掌握PHP语言的基础知识,以及在开发中比较常见的服务端组件的使用方法。接下来,当你看完本书第6章之后,我相信你应该会对如何使用PHP进行移动应用的服务端开发有了相当的认识。另外,和学习Android开发一样,我们同样要重视“优化篇”中关于PHP语言以及服务端优化的技巧,相信这些内容会让你的PHP编程技巧甚至服务端架构的功力更进一步。
在学习PHP的过程中一定要注意的是,要善于使用PHP的文档资源,最好是边学习、边动手、边查文档。另外,笔者一直认为PHP语言文档的完备程度是可以和大名鼎鼎的MSDN相比的。最后,要充分利用如下PHP的文档资源。
官方中文文档:http://www.php.net/manual/zh/
官方扩展库:http://pecl.php.net/packages.php
1.3.3同时学好Android和PHP
也许在以前,同时学习Android系统和PHP语言是一件很不可思议的事情,但是,在有了本书之后,同时学好这两种主流的技术不再只是一个梦想。当然,我们更不用怀疑,能同时学好Android和PHP两种技术绝对是一件一举两得的好事!
首先,编程的技术其实是相通的,每门编程语言都有自己的优势和缺点,就拿Java和PHP来说,良好的类库设计和面向对象思想是Java的优点,那么在学习的时候我们就应当思考如何把这些优点运用到PHP的程序设计中去;而简单方便的字符串和数组操作是PHP的优势,那么我们在学习Java的时候就需要考虑怎么把这部分的接口方法设计得更简洁一些。假如我们在学习Android和PHP的过程中,懂得使用类似以上提到的“取长补短”式的思路进行学习,不仅大大有益于我们对这两种技术的学习和运用,甚至还可以加强日后学习其他技术的能力。
其次,大家应该都知道目前市场上最紧缺的就是综合性的人才,对于移动互联网领域来说,既掌握Android客户端开发,又通晓PHP服务端编程的开发者绝对是移动互联网领域最受欢迎的技术人才之一。此外,根据笔者多年的职场经验来看,多掌握几种技术总归是一件好事,很难说在未来的哪一天就可能会派上大用场。另外,如果你对技术方面有更长远的目标,笔者也很希望本书能成为你踏上成功之路的一块踏板。
回到如何学习Android和PHP的问题上来。首先,我们需要清楚的是:Android代表的是客户端开发,而PHP涉及的则是服务端开发,要想把两者结合起来,我们必须通过一个第三方的文本协议JSON。对JSON不熟悉的朋友可以先学习一下本书3.3节的内容。另外,Android客户端开发和PHP服务端开发,使用的是两种完全不同的语言,要同时学好两者当然不是一件容易的事情。因此,在学习的时候,我们要注意采用“比对式”的方式去学习和思考Android和PHP这两套不同的知识体系;同时,我们也需要注意怎样使用JSON协议把这两套系统联合起来,形成一个整体。
总之,想要同时学好Android和PHP,不仅要求大家有比较坚实的编程基础知识,还需要注意学习和思考的方式,把两者看做一个整体来进行比对学习。本书在“准备篇”中把Android和PHP开发的基础知识讲解完之后,还会在“实战篇”中给大家安排“微博应用”作为实例进行讲解,该应用是一个把Android客户端开发和PHP服务端开发相结合的绝佳案例,大家可以边学习理解、边动手研究。如果读完本书之后,你已经对Android加PHP的这套技术解决方案了然于胸的话,那么我要恭喜你已经跨出了迈向成功的重要一步。
1.4小结
在本章中,我们实际上讨论了几个前期问题:为什么要学习Android移动互联网应用开发?为什么要使用Android和PHP的架构来进行开发?如何学习?相信现在大家都已经找到自己的答案了,那么在以下的章节中我们就要开始正式地学习如何开发了。在第2章和第3章中,我们将分别学习Android和PHP的开发基础和技巧。
第2章Android开发准备
在开始学习Android开发之前,让我们先来了解一个有趣的Android小知识:Android一词最早出现于法国作家利尔亚当在1886年发表的科幻小说《未来夏娃》中,书中将外表像人的机器起名为Android(不知道是不是和Angel同音的缘故),正因为如此,Android的商标也是一个绿色的小机器人。直至今日,大家都知道Android代表的是Google推出的开源智能移动终端操作系统。
本章将先对Android系统框架、Android应用框架以及Android应用开发过程中的几个要点做一个整体性的介绍,让大家尽快做好Android应用开发的准备工作。另外,在本章的最后两节,我们还将学会如何安装Android开发环境和Android开发的必备工具(Eclipse加ADT),并建立你的第一个Android项目,即Hello World项目,由此开始你的Android开发之旅。
2.1Android背景知识
Android是一种基于Linux平台的、开源的、智能移动终端的操作系统,主要使用于便携设备,Android操作系统最初由Andy Rubin开发,主要支持手机设备。2005年由Google收购注资,并召集多家制造商组成“开放手机联盟”对其进行开发改良,并逐渐扩展到平板电脑及其他领域,近年来逐渐成为主流的移动终端操作系统之一。
Android平台的研发队伍十分强大,包括Google、HTC、T-Mobile、高通、摩托罗拉、三星、LG以及中国移动在内的30多家产商都将基于该平台开发手机新型业务。当然,使用Android这个统一的平台进行开发,对于我们开发者来说也是一大福音,至少在软件应用的通用性方面,我们不需要过多考虑。但是,你知道吗?如此强大的Android系统实际上才刚满4周岁,从2008年9月发布的Android 1.0开始,在接下来的几年中,Android一直在以惊人的速度成长着,直到今天成为占领全球半数市场的“巨无霸”,这个成绩可以算得上是一个奇迹了。让我们通过下表来回顾一下Android的成长之路吧!
表2-1Android成长之路
版本发布时间主要改进
Android 1.52009年4月1. 拍摄/播放影片
Cupcake2. 支持立体声蓝牙耳机
3. 最新的采用WebKit技术的浏览器
4. 支持复制/粘贴和页面中搜索
(续)
版本发布时间主要改进
5. GPS性能大大提高
6. 提供屏幕虚拟键盘
7. 应用程序自动随着手机旋转
8. 短信、Gmail、日历,浏览器的用户接口大幅改进
9. 相机启动速度加快,来电照片显示
Android 1.62009年9月1. 重新设计的Android Market手势
Donut2. 支持CDMA网络
3. 文字转语音系统(Text-to-Speech)
4. 快速搜索框
5. 全新的拍照接口
6. 查看应用程序耗电
7. 支持虚拟专用网络(VPN)
8. 支持更高的屏幕分辨率
9. 支持OpenCore 2媒体引擎
10. 新增面向视觉或听觉困难人群的易用性插件
Android 2.0/2.1/2.22009年10月1. 优化硬件速度
Eclair2. 增加“Car Home”程序
3. 支持更高的屏幕分辨率
4. 改良的用户界面
5. 新的浏览器的用户接口和支持HTML 5
6. 新的联系人名单
7. 更好的白/黑色背景比率
8. 改进Google Maps 3.1.2
9. 支持Microsoft Exchange
10. 支持内置相机闪光灯
11. 支持数码变焦
12. 改进的虚拟键盘
13. 支持蓝牙2.1
14. 支持动态桌面的设计
Android 2.1/2.2.12010年5月1. 整体性能大幅度提升
Froyo2. 3G网络共享功能
3. Flash的支持
4. App2sd功能
5. 全新的应用商店
6. 更多的Web应用API接口的开发
Android 2.32010年12月1. 增加了新的垃圾回收和优化处理事件
Gingerbread2. 原生代码可直接存取输入和感应器事件
3. 支持EGL/OpenGL ES、OpenSL ES
4. 新的管理窗口和生命周期的框架
5. 支持VP8和WebM视频格式,提供AAC和AMR宽频编码,提供了新的音频效果器
6. 支持前置摄像头、SIP/VOIP和NFC(近场通信)
7. 简化界面、速度提升、优化文字输入/复制/粘贴等
8. 改进的电源管理系统、新的应用管理方式
(续)
版本发布时间主要改进
Android 3.02011年2月1. 针对平板的优化
Honeycomb2. 全新设计的UI增强网页浏览功能
3. 增加n-app purchases功能
Android 3.12011年5月1. 优化Gmail电子邮箱
Honeycomb2. 全面支持Google Map
3. 将Android手机系统跟平板系统再次合并
4. 任务管理器可滚动,支持USB 输入设备(键盘、鼠标等)
5. 支持 Google TV,可以支持XBOX 360无线手柄
6. 更加容易地定制屏幕widget插件
Android 3.22011年7月1. 支持更多屏幕尺寸的设备
Honeycomb2. 引入了应用显示缩放功能
Android 4.02012年1. 增强任务系统,人性化系统手势
Ice Cream2. 优化UI,支持自动缩放
3. 增强语音功能
4. 增强云服务
Android N.n未知继Ice Cream之后的下一版Android系统
Jelly Bean
从上表中,大家不仅可以了解Android系统的发展历程,而且可以了解Android系统在功能改进上的一些细节。另外,需要大家注意的是,考虑到对目前大部分设备的兼容性,本书下面的项目实例是在Android 2.2版本上安装/调试的。
2.2Android系统框架
在开始介绍Android应用开发之前,我们先来了解一下Android的系统框架。虽然,是否了解Android系统框架与能否进行Android应用开发之间没有任何必然的联系,但是在学习Android的过程中,这个部分内容却是必不可少的,因为能否理解Android的系统架构对于你日后能否对Android进行更深入的学习是至关重要的。首先,我们来看一张不得不说的图,也就是Google官方公布的Android的系统框架图,如图2-1所示。
从图2-1展示的Android系统架构图可以很清晰看出,Android系统分为四层:应用层、应用框架层、系统类库层和系统内核层。下面我们将对这四个层次做一些简要的分析和介绍。
1. 应用层(Applications)
应用层(Applications)是指运行于Android虚拟机上的程序,也就是开发者们平时开发的“手机应用”。在系统应用层里,我们可以通过Android提供的组件和API进行开发,从而编写出形形色色、丰富多彩的移动软件和游戏。
2. 应用框架层(Application Framework)
应用框架层(Application Framework)是Android应用开发的核心,为开发者开发应用时提供基础的API框架。当然,Android本身的很多核心应用也是在这层的基础上开发的。下面我们就来了解一下这些模块的作用(见表2-2)。
图2-1Android系统框架
表2-2应用框架层主要模块
模块名模块简介
1View System主要用于UI设计,包括列表(List)、网格(Grid)、文本框(Text)、按钮(Button)以及嵌入式Web浏览器(WebView)等
2Activity Manager负责管理应用程序中Activity的生命周期以及提供Activity之间的切换功能(Intent相关)
3Window Manager用于管理所有的窗口程序,如Dialog、Toast等
4Resource Manager提供非代码资源的管理,如布局文件、图形、字符串等
5Location Manager负责与定位功能LBS(Location Based Service)相关功能
6Content Providers提供了一组通用的数据访问接口,可用于应用程序间的内容交互,比如可以用于获取手机联系人数据等
7Package Manager Android系统内的包管理模块,负责管理安装的应用程序
8Notification Manager用于管理手机状态栏中的自定义信息等
9Telephony Manager手机底层功能管理模块,可用于获取手机串号或者调用短信功能
10XMPP Service用于支持XMPP协议的服务,比如与Google Talk通信等
以上列出的模块都是我们在应用开发中经常用到的,大家可以先熟悉一下。其中最核心的Activity Manager和View System我们将分别在2.3节和2.7节中作详细介绍。此外,其他常用的Android模块的相关内容我们也会在本书以后的章节中穿插介绍。
3. 系统类库层(Libraries)
为了支持上层应用的运行,Android会通过系统类库层(Libraries)中的一些比较底层的C和C++库来支持我们所使用的各个组件或者模块。以下列举一些比较重要的类库的功能,这个部分大家了解即可。
Surface Manager:负责管理显示与存储之间的互动,以及对2D绘图和3D绘图进行显示上的合成。Android中的图形系统实际上采用的是C/S结构,Client端就是应用程序,而Server端是Surface Flinger,Client端通过Binder向Server端的Surface Flinger传输图像数据,最终由Surface Flinger合成到Frame Buffer中,然后在屏幕上显示出来。
Media Framework:Android的多媒体库,该库支持多种常见格式的音频和视频的播放、录制等各种操作,比如JPG、PNG、MPEG4、MP3、AAC、AMR等。
SQLite:Android自带的关系数据库,可用于存储复杂数据。
OpenGL/ES:3D效果库,主要用于3D游戏开发。
FreeType:支持位图、矢量、字体等。
WebKit:Android的Web浏览器内核(和iOS一样)。
SGL:2D图形引擎库。
SSL:安全数据通信支持。
Libc:也就是Bionic系统C库,当前支持ARM和x86指令集。该库非常小巧,主要用于系统底层调用,在NDK中经常会使用到。
4. 系统内核层(Linux Kernel)
Android内核具有和标准的Linux内核一样的功能,主要实现了内存管理、进程调度、进程间通信等功能。就最新的Android内核源码树的根目录结构来看,Android 内核源码与标准 Linux 内核并无不同;但是,经过与标准 Linux 内核源代码进行详细对比,可以发现Android内核与标准Linux内核在文件系统、进程间通信机制、内存管理等方面存在着不同。当然,了解它们之间的区别对进一步了解Android系统是有很大帮助的,下面我们从几个方面来分析两者之间的异同。
文件系统。不同于桌面系统与服务器,移动设备采用的大多不是硬盘而是 Flash 作为存储介质。因此,Android 内核中增加了标准 Linux 专用于 Flash 的文件系统 YAFFS2。YAFFS2 是日志结构的文件系统,提供了损耗平衡和掉电保护,可以有效地避免意外断电对文件系统一致性和完整性的影响。经过测试证明,YAFFS2 性能比支持 NOR 型闪存的 JFFS2 文件系统更加优秀。YAFFS2对Nand-Flash芯片也有着良好的支持。
进程间通信机制。Android 增加了一种进程间的通信机制 IPC Binder。Binder 通过守护进程 Service Manager 管理系统中的服务,负责进程间的数据交换。各进程通过 Binder 访问同一块共享内存,以达到数据通信的机制。从应用层的角度看,进程通过访问数据守护进程获取用于数据交换的程序框架接口,调用并通过接口共享数据,而其他进程要访问数据,也只需与程序框架接口进行交互,方便了程序员开发需要交互数据的应用程序。
内存管理。在内存管理模块上,Android 内核采用了一种不同于标准 Linux 内核的低内存管理策略。Android 系统采用的是一种叫作 LMK(Low Memory Killer) 的机制,这种机制将进程按照重要性进行分级、分组,内存不足时,将处于最低级别组的进程关闭,保证系统是稳定运行的。同时,Android 新增加了一种内存共享的处理方式 Ashmem(Anonymous Shared Memory,匿名共享内存)。通过Ashmem,进程间可以匿名自由共享具名的内存块,这种共享方式在标准 Linux 当中也是不被支持的。
电源管理。由于 Android 主要用于移动设备,电源管理就显得尤为重要。不同于标准Linux内核,Android 采用的是一种较为简单的电源管理策略,通过开关屏幕、开关屏幕背光、开关键盘背光、开关按钮背光和调整屏幕亮度来实现电源管理,并没有实现休眠和待机功能。目前有三种途径判断调整电源管理策略:RPC调用、电池状态改变和电源设置。系统通过广播 Intent 或直接调用 API 的方式来与其他模块进行联系。电源管理策略同时还有自动关机机制,当电力低于最低可接受程度时,系统将自动关机。另外,Android 的电源管理模块还会根据用户行为自动调整屏幕亮度。
驱动及其他。相对于标准内核,Android 内核还添加了字符输出设备、图像显示设备、键盘输入设备、RTC 设备、USBDevice 设备等相关设备驱动,增加了日志(Logger)系统,使应用程序可以访问日志消息,使开发人员获得更大的自由。
2.3Android应用框架
前面介绍了Android的系统框架,主要目的是让大家对Android系统有整体的概念,也为日后更深入的学习打好基础。然而,目前我们更需要重点学习和掌握的则是Android的应用框架,因为是否能掌握和理解Android应用框架,直接关系到是否能学好Android应用开发。
Android的应用框架是一个庞大的体系,想要理解透彻并不是那么简单的事情,但是,好在其中有一些比较清晰的脉络可以帮助我们快速地熟悉这个系统,因此抓住这些脉络中的核心要点对于能否学好Android的应用开发来说是至关重要的。一般来说,Android应用框架中包含四个核心要点,即活动(Activity)、消息(Intent)、视图(View)和任务(Task)。
如果你觉得上述核心要点的概念很陌生,不好理解,那么我们来看看下面这个比喻:如果把一个Android应用比喻成海洋,那么每个Activity就是这个海洋中的岛屿,假设我们眼前有一项任务(也就是Task),需要我们在其中若干个岛屿上建立起自己的王国。于是问题来了,我们要怎么样从一座岛屿去到另一座岛屿呢?没错,我们需要交通工具,而Intent就是我们最重要的交通工具。当然,Intent不仅可以带我们去,而且还可以帮我们带上很多需要的东西。接着,到了岛上,我们开始建立一个自己的王国,要知道这可需要很多的资源,这个时候,我们就会想到View这个建筑公司,因为他可以帮助我们快速地建出我们需要的东西。这样,Activity、Intent、View以及Task一起配合完成了一个完整的Android应用的王国。
从以上的比喻中,我们还可以认识到,在这四个要点中,Activity是基础,Intent是关键,View是必要工具,而Task则是开发的脉络。对于开发者来说,只有掌握了Activity、Intent、View和Task这几个核心要素之后,才能够做出多种多样的应用程序。接下来,让我们分别学习一下这四个核心要点。
2.3.1活动(Activity)
活动(Activity)是Android应用框架最基础、最核心的内容和元素,每个Android应用都是由一个或者若干个Activity构成的。在Android应用系统中,Activity的概念类似于界面,而Activity对象我们通常称之为“界面控制器”(从MVC的角度来说)。从另一个角度来理解,Activity的概念比较类似于网站(Web)开发中“网页”的概念。此外,当Android应用运行的时候,每个Activity都会有自己独立的生命周期,图2-2所示的就是Activity的生命周期。
图2-2Activity生命周期
其实,在Android系统内部有专门的Activity堆栈(Stack)空间,用于存储多个Activity的运行状态。一般来说,系统会保证某一时刻只有最顶端的那个Activity是处于前端的活动(foreground)状态。也正因如此,一个Activity才会有如图2-2所示的生命周期。当一个Activity启动并进入活动状态的时候,调用顺序是onCreate、onStart、onResume;退居后台的时候,调用顺序是onPause、onStop;重新回到活动状态的时候,调用顺序是onRestart、onStart、onResume;销毁的时候,调用顺序是onPause、onStop、onDestroy。我们应该深刻理解这些状态的变化过程,因为在Android应用开发的过程中我们会经常用到。至于如何更好地掌握Activity的特性,大家可以尝试将以下代码(代码清单2-1)放入Android应用中运行,并对照程序打印出的调试信息来理解Activity生命周期各阶段的使用。
代码清单2-1
// 基础Activity类,用于测试
public class BasicActivity extends Activity {
private String TAG = this.getClass().getSimpleName();
public void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "TaskId:"+this.getTaskId());
}
public void onStart() {
super.onStart();
Log.w(TAG, "onStart");
}
public void onRestart() {
super.onStart();
Log.w(TAG, "onRestart");
}
public void onResume() {
super.onResume();
Log.w(TAG, "onResume");
}
public void onPause() {
super.onPause();
Log.w(TAG, "onPause");
}
public void onStop() {
super.onStop();
Log.w(TAG, "onStop");
}
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "onDestroy");
}
public void onNewIntent() {
Log.w(TAG, "onNewIntent");
}
}
此外,所有的Activity必须在项目基础配置文件AndroidManifest.xml中声明,这样Activity才可以被Android应用框架所识别;如果你只写了Java代码而不进行声明的话,运行时就会抛出ActivityNotFoundException异常。关于Activity声明的具体操作,我们会在2.10.2节中结合Hello World项目进行详细介绍。
2.3.2消息(Intent)
参考之前我们对Android应用框架的几个核心要点的比喻,我们应该知道Intent消息模块对于Android应用框架来说有多重要;如果没有它的话,Android应用的各个模块就像一座座“孤岛”,根本不可能构成一个完整的系统。在Android应用系统中,我们常常把Intent称为消息,实际上,Intent本身还是一个对象,里面包含的是构成消息的内容和属性,主要有如下几个属性,我们来分别认识一下。
1. 组件名称(ComponentName)
对于Android系统来说,组件名称实际上就是一个ComponentName对象,用于指定Intent对应的目标组件,Intent对象可以通过setComponent、setClass或者setClassName方法来进行设置。
2. 动作(Action)
消息基类(Intent)中定义了各种动作常量(字符串常量),其中比较常见的有:ACTION_MAIN(对应字符串android.intent.action.MAIN)表示应用的入口的初始化动作;ACTION_EDIT(对应字符串android.intent.action.EDIT)表示常见的编辑动作;ACTION_CALL(对应字符串android.intent.action.CALL)则表示用于初始化电话模块动作等。Intent对象常使用setAction方法来设置。
3. 数据(Data)
不同的动作对应不同的数据(Data)类型,比如ACTION_EDIT动作可能对应的是用于编辑文档的URI;而ACTION_CALL动作则应该包含类似于tel:xxx的URI。多数情况下,数据类型可以从URI的格式中获取,当然,Intent也支持使用setData、setType方法来指定数据的URI以及数据类型。
4.类别(Category)
既然不同的动作应该对应不同的数据类型,那么不同的动作也应该由不同的类别的Activity组件来处理,比如CATEGORY_BROWSABLE表示该Intent应该由浏览器组件来打开,CATEGORY_LAUNCHER表示此Intent由应用初始化Activity处理;而CATEGORY_PREFERENCE则表示处理该Intent的应该是系统配置界面。此外,消息对象(Intent)可以使用addCategory添加一种类型,而一个Intent对象也可以包含多种类型属性。
5. 附加信息(Extras)
一个Intent对象除了可以包含以上的重要信息之外,还可以存储一些自定义的额外附加信息,一般来说,这些信息是使用键值对(key value)的方式存储的。我们可以使用putExtra方法设置附加信息,信息类型非常丰富(一般还是以字符串为主);在接收的时候使用getExtras方法获取。
6. 标志(Flags)
除了上面提到的几个功能属性,消息基类中还定义了一系列特殊的消息行为属性(也就是标志),用于指示Android系统如何去启动Activity以及启动之后如何处理。关于标志(Flags)的使用我们还会在2.3.4节中介绍。
在Android应用中,消息(Intent)的使用方式通常有两种,一是显式消息(Explicit Intent),另一个则是隐式消息(Implicit Intent)。显式消息的使用比较简单,只需要在Intent中指定目标组件名称(也就是前面提到的ComponentName属性)即可,一般用于目标Activity比较明确的情形。比如在一个固定流程中,我们需要从一个Activity跳转到另一个,那么我们就会使用显式的消息。而隐式消息则比较复杂一点,它需要通过消息过滤器(IntentFilter)来处理,一般用于目的性不是那么明确的情形,比如应用中的某个功能需要往目的地发送消息,但是我们却不确定要使用短信发送还是微博发送,那么这个时候就应该使用隐性消息来处理了。下面是一个典型的消息过滤器的配置范例,如代码清单2-2所示。
代码清单2-2
<activity...>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" android:mimeType="image/*" />
</intent-filter>
</activity>
我们看到,配置消息过滤器使用的是<intent-filter/>标签,一般需要包含三个要素:action、category以及data。其中,action是必需的,category一般也是需要的,而data则允许没有设置。接下来,我们学习一下这几个要素的使用方法。
<action/>:在Android应用中,一般会通过<action/>元素来匹配消息(Intent),如果找到Action就表明匹配成功,否则就是还没找到目标。需要注意的是,如果消息过滤器没有指定<action/>元素,那么此消息只能被显式消息匹配上,不能匹配任何的隐式消息;相反,当消息没有指定目标组件名称时,可以匹配含有任何包含<action/>的消息过滤器,但不能匹配没有指定<action/>信息的消息过滤器。
<category/>:<category/>元素用于标注消息的类别。值得注意的是,假如我们使用<category/>元素来标识消息类别,系统在调用Context.startActivity方法或者Context.startActivityForResult方法时都会自动加上DEFAULT类别。因此,除了Intent已经指定为Intent.ACTION_MAIN以外,我们还必须指定<category/>为android.intent.category.DEFAULT,否则该消息将不会被匹配到。另外,对于Service和BroadcastReceiver,如果Intent中没有指定<category/>,那么在其消息过滤器中也不必指定。
< data/>:通过data字段来匹配消息相对来讲比较复杂,通常的data字段包含uri、scheme(content, file, http)和type(mimeType)几种字段。对于Intent来说,我们可以使用setData和setType方法来设置,对于IntentFilter来讲,则可以通过android:scheme和android:mimeType属性分别来指定,使用范例如代码清单2-3所示。
代码清单2-3
<activity ...>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" android:mimeType="image/*" />
</intent-filter>
</activity>
以上的配置表明该Activity可以发送图片,而且内容必须是单独的一个文件,也就是说,该文件的URI路径必须是以“file://”开头的。当然,如果我们把这里的“android:scheme”改成“content”的话,则表明该图片内容必须是由ContentProvider提供的,即URI必须是以“content://”开头的。
至此,我们已经介绍了消息(Intent)和消息过滤器(IntentFilter)的基本概念和用法。我们必须清楚的是,消息分为显式消息和隐式消息两种,而消息过滤器一般是提供给隐式消息使用的。Android消息过滤器的过滤规则比较严格,只要我们申明了除了默认值(DEFAULT)之外的action、category和data,那么,只有当对应消息对象的动作(action)、类别(category)和数据类型(data)同时符合消息过滤器的配置时才会被考虑。关于<intent-filter/>标签的具体使用方法,我们将会在本书7.2.4节中结合实例进行讲解。
2.3.3视图(View)
视图(View)系统主管Android应用的界面外观显示,因此也称作Android UI系统,是Android应用框架中最重要的组成部分之一。我们在Activity中展示或者操作的几乎所有控件都属于View。Android应用框架的View System包含View和ViewGroup两类基础组件。下面我们来理解一下Android视图系统的层次结构,如图2-3所示。
视图类(View)是所有视图(UI)控件(包括ViewGroup)的基类。视图组(ViewGroup)则类似于集合,一个视图组可以包含多个ViewGroup和View,类似于Html标签中的层(div)。接下来,我们再来看看View中会经常使用的一些UI控件(见表2-3),你也可以在Android SDK参考文档(Reference)中的android.widget包下找到它们。
从表2-3中可以看出,Android应用框架为我们提供了非常丰富的视图控件,从某种程度上来说,Android应用的界面是通过各种各样的视图控件组合起来的。至于这些视图控件的具体用法,我们将在第7章中结合项目实例进行介绍。
表2-3 Android主要UI控件
主要控件说明
Button普通按钮
CheckBox多选框控件
EditText编辑框控件
Gallery图片集控件
GridView格子显示控件
ImageButton图片按钮
ImageView图片控件
LinearLayout线性布局
ListPopupWindow弹出式多选框
ListView列表控件
PopupMenu弹出菜单
PopupWindow弹出窗口
ProgressBar进度条控件
RadioButton单选框控件
RelativeLayout绝对定位布局
ScrollView滚动式列表
TableLayout表格布局
TextView文本框
Toast弹出提示框
本节只是从应用程序框架组成部分的角度简单地介绍了Android UI系统的概念,关于UI系统的更多知识以及UI控件的具体用法,我们将在本章2.7节中更系统地介绍。
2.3.4任务(Task)
本节介绍Android任务(Task)的概念。区别于以上介绍的活动、消息和视图这几个要点,任务的概念显得比较抽象,且我们在日常编码过程中也不会直接接触到,但是,理解任务却是理解整个Android应用框架的关键。
首先,我们来认识一下Android系统中的任务是如何运行的。简单来说,当我们在手机的应用列表(Application Launcher)中点击某个应用图标的时候,一个新的Task就启动了,后面的操作可能会涉及多个应用中不同Activity的界面,而这些Activity的运行状态都会被存储到Task的Activity堆栈(Activity Stack)中去。和其他的堆栈一样,Activity堆栈采用的是“后进先出”的规则。图2-4展示就是一个常见任务中Activity堆栈的变化情况。
每次启动一个新的Activity,其都会被压入(push)到Activity堆栈的顶部,而每次按“BACK”键,当前的Activity就会被弹出(pop)Activity堆栈;另外,如果按了“HOME”键的话,该Task会失去焦点并被保存在内存中;而一旦重新启动,Task会自动读出并显示上次所在的Activity的界面。那么,从一个应用进入另一个应用的情况是怎样呢?比如,应用中需要配置一些系统设置,那么我们就需要考虑一下多任务切换的情况了,如图2-5所示。
图2-4单任务模式中Activity堆栈的变化
图2-5多任务模式中Activity堆栈的变化
我们假设Task A是应用A的任务,也是我们所在的任务,当运行到Activity 3的时候我们按了“Home”键,于是Task A中的所有Activity就都被停止了,同时Task A暂时退居到后台(Background);这时,我们点击应用B的图标激活了Task B,于是Task B就被推到了前台(Foreground),并展示出最上层的Activity Z;当然,我们还可以用类似的操作把Task A激活并放置到前台进行操作。以上也是我们使用Android系统最经常使用的行为操作,大家可以结合实际情况好好理解一下。
以上的策略已经可以满足大部分Android应用的需求。此外,Android还提供了一些其他的策略来满足一些特殊的需求。比较常见的,如我们可以在Android基础配置文件(Menifest File)中使用<activity/>元素的launchMode属性来控制Activity在任务中的行为特征。launchMode有以下四种模式可供选择。
Standard模式:Standard模式为默认模式,无论是打开一个新的Activity,还是接收Intent消息,系统都会为这个Activity创建一个新的实例(instance);每个Activity都可以被实例化多次,并且每个任务都可以包含多个实例。此模式最常用,但是其缺点就是太耗费系统资源。
singleTop模式:该模式下的行为和Standard模式下的行为基本相同,如果该Activity正好在运行状态(也就是在Activity堆栈的顶部),那么其接收Intent消息就不需要重新创建实例,而是通过该类的onNewIntent()方法来处理接收到的消息。这种处理方式在一定程度上会减少一些资源浪费。
singleTask模式:此模式保证该Activity在任务中只会有一个实例,并且必须存在于该Task的根元素(即栈底)。此模式比较节省资源,手机浏览器使用的就是这种模式。
singleInstance模式:此模式与singleTask模式类似,不同之处是该模式保证Activity独占一个Task,其他的Activity都不能存在于该任务的Activity堆栈中。当然,Activity接收Intent消息也是通过onNewIntent方法实现。
此外,我们还可以通过设置Intent消息的flag标志来主动改变Activity的调用方式,比较常见的flag如下。
FLAG_ACTIVITY_NEW_TASK:在新的Task中启动目标Activity,表现行为和前面提到的singleTask模式下的行为一样。
FLAG_ACTIVITY_SINGLE_TOP:如果目标Activity正好位于堆栈的顶部,则系统不用新建Activity的实例并使用onNewIntent()方法来处理接收到的消息。表现行为和前面提到的singleTop模式下的行为一样。
FLAG_ACTIVITY_CLEAR_TOP:如果目标Activity的运行实例已经存在,使用此方法系统将会清除目标Activity所处的堆栈上面的所有Activity实例。
需要注意的是,官方文档中建议多使用默认的Task行为模式,因为该模式比较简单也易于调试。对于一些特殊的需求,如果需要使用到其他模式的话,需要模拟不同的情况多进行一些测试,以防止在一些特殊情况下出现不符合预期的情况。当然,说句实话,目前主流移动设备上的Android版本都还比较旧,对多任务管理的支持和体现还不够明显,不过,我们应该可以在Android最新版本(如Android 4.0)里看到对系统任务管理功能的加强。
2.4Android系统四大组件
之前我们已经学习了Android应用框架的四大核心要点,对Android的应用框架有了一个总体性的了解,接下来我们要学习Android应用程序中的四个重要组成部分,也就是我们一般所说的“应用组件”。在前面讲解四大核心要点的篇幅中,我们曾经提到了控件(View控件)的概念,现在我们再来学习一下Android应用框架中的组件的概念。那么何谓组件呢?顾名思义,组件当然要比控件复杂,简而言之,组件是用于工业化组装的部件。要达到组件的标准,必须符合三个要求,以下我们结合Android应用框架讨论如下。
1. 有统一标准
这点应该是形成组件的前提条件,试问,组件如果没有标准,如何组装?在这点上,Android应用框架中定义了很多标准接口,满足了组件间的各种接口需求;换一种说法,整合Android系统都是按照接口规范设计出来的。
2. 可独立部署
组件应该是独立的,每个组件都有自成一套的功能体系,否则就没有形成组件的必要。比如每个Activity都是可以独立构造的,使用Activity组件,我们可以完成一个包含许多复杂功能的界面;而使用Service,我们可以操作一个独立的后台进程等。
3. 可组装整合
可组装是组件最重要的特性,一个完整的Android应用必然是若干个系统组件构成的,这就要求组件必须是能组装在一起的,至于如何组装,我们会在后面的章节中结合实例进行介绍。
通常来讲,Android应用框架中包含了四大组件:活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供者(Content Provider)。这四大组件除了具有前面所提到的三个特点之外,还有着相同的显著特点,那就是它们都可以在Android的基础配置文件,即AndroidManifest.xml中进行配置。下面我们就来学习Android系统四大组件的基本概念和使用方法。
2.4.1活动(Activity)
在2.3.1节中,我们已经介绍了Android活动(Activity)的生命周期以及基本行为,大家应该对Activity的概念有了一定的了解。此外,Activity同时还是Android系统四大组件中的一员,因此,本节将着重介绍Activity作为组件的一般声明方法。
说到Activity的声明方法,我们必须先了解Android全局配置文件AndroidManifest.xml的基础知识。每个Android应用项目都会有自己的全局配置文件,该文件包含了应用的系统常量、系统权限以及所含组件等配置信息。配置使用范例如代码清单2-4所示。
代码清单2-4
<manifest ...>
<application ...>
<activity android:name="com.app.android.HelloActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape">
<intent-filter>
...
</intent-filter>
<intent-filter>
...
</intent-filter>
</activity>
…
</application>
<uses-permission .../>
<uses-permission .../>
...
</manifest>
从上述配置使用范例中,我们可以看到在AndroidManifest.xml配置文件范例的根元素<manifest/>下面有两种标签,即<application/>和<uses-permission/>元素。前者是应用配置的根元素,而后者则用于配置应用的权限。这里顺便说一下,每个Android应用都必须事先声明应用需要的权限,比如是否需要网络、是否需要使用摄像头或者是否需要打开卫星定位(GPS)等。而用户在安装该应用之前,系统会先提示用户是否允许该应用使用这些权限,如果用户觉得应用不安全便可以选择不安装,这在一定程度上也提高了Android系统的安全性。
另外,我们还可以看到,在以上配制文件中的<application/>元素里面含有一个或者若干个<activity/>元素,这个就是我们需要重点了解的Activity标签了。首先,我们来看一下该标签内部的一些常用的配置选项。
android:name:表示该Activity对应的类的名称,在代码清单2-4中,我们就定义了一个Activity,它的具体类包名就是“com.app.android.HelloActivity”。
android:theme:表示Activity所使用的主题,在Android系统中是允许我们自定义主题的(这部分的内容我们在后面章节的实例中会介绍到),在代码清单2-4中,使用的是默认主题“@android:style/Theme.NoTitleBar.Fullscreen”,也就是全屏模式。
android:launchMode:Activity的行为模式,之前在2.3.4节中介绍过该标签的4种选项,即与任务行为有关的Standard、singleTop、singleTask以及singleInstance。
android:screenOrientation:表示屏幕的方向,在代码清单2-4中,landscape表示的是该Activity是横屏显示的,如果改成portrait的话,则就变成竖屏显示。
当然,Activity标签可配置的选项远不止以上这些,更详细的使用说明可以参考7.1.2节的内容,使用范例可参考代码清单7-11。此外,从上面的配制文件中我们还可以看到不止一个<intent-filter>元素。关于这点,实际上,前面我们已经介绍过消息过滤器的用法,如果大家有疑问的话,可以参考2.3.2节中与消息(Intent)相关的内容。
另外,Activity在应用开发中被用做控制界面的逻辑,也就是MVC中的Controller控制器,关于Android应用中MVC的概念可参考5.2.3节中的内容。开发者可以根据需要,在Activity的生命周期方法中添加不同的逻辑来控制对应应用界面的显示、动作和响应等,而Activity类的具体用法和代码示例我们可以在本书第7章的“微博实例”代码中学习到。
2.4.2服务(Service)
Android系统中的Service服务组件和Windows系统中的后台服务有点类似,这个概念应该很容易理解,比如,我们在退出某些聊天软件之后还是可以接收到好友发来的消息,就是使用Android服务组件来实现的。此外,如果需要在应用后台运行某些程序,Service服务组件也绝对是最佳的选择。另外,值得注意的是,Service和之前的Activity一样,也有自己的生命周期,但是,Service的生命周期相对简单一些,如图2-6所示。
从图2-6中我们可以看出Android服务(Service)主要有以下两种运行模式。
独立运行模式:我们一般通过“startService()”方法来启动一个独立的服务,在这种模式下,该服务不会返回任何信息给启动它的进程,进程的动作结束后会自动结束。比如,浏览器下载就属于独立服务。
绑定运行模式:与独立服务不同,绑定服务是与启动它的应用绑定在一起的,当该应用结束的时候,绑定服务也会停止。另外,这种服务可以和应用中的其他模块进行信息交互,甚至进行进程通信(IPC)。
图2-6Service生命周期
与Activity类似,onCreate和onDestroy分别是Android服务创建和销毁过程中的回调方法。与独立运行模式相比,绑定运行模式中多出来onBind和onUnbind两个函数,分别是服务绑定和解绑过程的回调方法。在Android应用开发的时候,我们通常会使用startService方法来开启Service服务。另外,在应用开发的时候千万别忘了我们必须事先在全局配置文件中进行如下声明,如代码清单2-5所示。
代码清单2-5
<application ...>
<service android:name=".HelloService"/>
<activity ...>
...
</activity>
</application>
理解Android服务(Service)时要特别注意,千万不要盲目认为服务是一个独立的进程或者线程。实际上,它和应用程序的进程之间存在着复杂的联系,所以如果我们需要在Service中做一些耗时操作的话,必须新起一个线程并使用消息处理器Handler来处理消息。另外,Android服务的进程间通信(IPC)功能还涉及AIDL(Android Interface Definition Language,Android接口定义语言),有兴趣的话尽管去了解一下。关于Service的具体使用实例,大家可以先去看看Android SDK中API Demos里面的RemoteService实现,本书后面的实例中我们也会穿插介绍。
小贴士:Handler是消息处理器,用于接受子线程的消息进行处理并配合主线程更新UI界面,具体内容可参考5.2.2节中界面基础类BaseUi的相关内容。
在Android系统中,Service服务类的使用方法比较简单,执行Service对象的start方法就可以开启一个服务。实际上,第7章的“微博实例”中也有与Service服务相关的代码实例,请参考7.5.4节。
2.4.3广播接收器(Broadcast Receiver)
广播接收器(Broadcast Receiver)是Android系统的重要组件之一,可以用来接收其他应用发出来的广播,这样不仅增强了Android系统的交互性,而且能在一定程度上提高用户的操作体验。比如,你在把玩应用或者游戏的同时也可以随时接收一条短信或者一个电话,或者你在打开网页的同时还可以接收短信验证码等。
广播接收器的使用也很简单,和其他组件的步骤一样:先声明,再调用。代码清单2-6就是一个声明广播接收器的例子。
代码清单2-6
<application ...>
<receiver android:name=".HelloReceiver">
<intent-filter>
<action android:name="com.app.basicreceiver.helloreceiver"/>
</intent-filter>
</receiver>
<activity ...>
...
</activity>
</application>
这里我们定义了一个名为HelloReceiver的广播接收器。这个类里面只有一个onReceive方法,里面我们可以定义需要的操作。使用的时候,我们可以在Activity中直接使用sendBroadcast方法来发送广播消息,这样HelloReceiver就会接收到我们发送的信息并进行相应的处理。这里需要注意的是,广播接收器也是在应用主线程里面的,所以我们不能在这里做一些耗时的操作,如果需要的话,可以新开线程来解决。发送广播消息的范例如代码清单2-7所示。
代码清单2-7
…
Intent intent = new Intent("com.app.basicreceiver.hello");
sendBroadcast(intent);
…
而接收消息的使用范例,也就是广播接收器类HelloReceiver的逻辑实现,我们可以参考代码清单2-8。
代码清单2-8
public class HelloReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Toast.makeText(context, "Receive Action : " + action, 1000).show();
}
}
另外,我们需要了解,Android系统中的广播消息是有等级的,可分为普通广播(Normal Broadcasts)和有序广播(Ordered Broadcasts)两种。前者是完全异步的,可以被所有的接收者接收到,而且接收者无法终止广播的传播;而有序广播则是按照接收者的优先级别被依次接收到。优先级别取决于intent-filter元素的android:priority属性,数越大,优先级越高。至于使用,我们通常会在onResume事件中通过registerReceiver进行注册,在onPause等事件中注销,这种方式使其能够在运行期间保持对相关事件的关注。常见的广播事件有:短信广播、电量通知广播等。
2.4.4内容提供者(Content Provider)
在Android应用中,我们可以使用显式消息(Explicit Intent)来直接访问其他应用的Activity,但是这仅限于Activity的范畴;如果需要使用其他应用的数据,还需要用到另外一种组件,这就是所谓的内容提供者(Content Provider)。
顾名思义,内容提供者就是Android应用框架提供的应用之间的数据提供和交换方案,它为所有的应用开了一扇窗,应用可以使用它对外提供数据。每个Content Provider类都使用URI(Universal Resource Identifier,通用资源标识符)作为独立的标识,格式如:content://xxx。其格式类似于REST,但是比REST更灵活,因为在调用接口的时候还可以添加Projection、Selection、OrderBy等参数,结果以Cursor的模式返回。Content Provider的声明写法非常简单,示例可参考代码清单2-9。
代码清单2-9
<application ...>
<provider android:name="com.app.android.HelloProvider"
android:authorities="com.app.android.HelloProvider"/>
<activity ...>
...
</activity>
</application>
关于Content Provider的类实现,我们只需要继承ContentProvider接口并实现其中的抽象方法即可,这几个方法有点类似于数据操作对象DAO的抽象方法,其中包括insert、delete、query和update这些常见的“增删查改”的接口方法。对于具体的数据存储来说,一般会使用Android的内置数据库SQLite,当然也可以采用文件或者其他形式的混合数据来实现。关于Android系统中的数据存储我们会在2.6节中介绍。
我们在使用上述四大组件的时候还需要注意的是:实际上,Service和Content Provider都可用于IPC(Inter-Process Communication,进程间通信),也就是在多个应用之间进行数据交换。Service可以是异步的,而Content Provider则是同步的。在某些情况下,在设计的时候我们要考虑到性能问题。当然,Android也提供了一个AsyncQueryHandler帮助异步访问Content Provider。关于以上四大组件的具体使用,我们会在后面的章节中穿插介绍。
另外,与Content Provider配合使用的还有Content Resolver,即内容处理器。前面也提到了Content Provider是以数据库接口的方式将数据提供出去,那么Content Resolver也将采用类似的数据库操作来从Content Provider中获取数据,而获取数据就需要使用query接口。和Content Provider类似,Content Resolver也需要使用URI的方式来获取对应的内容,其使用范例可参考7.3.2节中提到的Httputil类的相关代码(代码清单7-34)。
2.5Android上下文
大家对上下文(Context)的概念并不陌生,在软件开发领域,它主要用于存储进程或应用运行时的资源和对象的引用,此外,我们在接触其他系统和框架的时候也经常会碰到上下文的概念。当然,对于Android应用来说,上下文是非常重要的,这部分的内容在Android应用的实际开发中也会经常使用到,因此本节将会重点介绍Android上下文的相关知识,为后面实战编程打下一定的基础。
在Android应用框架中,根据作用域的不同,可以把上下文分为两种,一种是Activity界面的上下文,即Activity Context;另一种是Android应用的上下文,即Application Context。下面我们分别介绍这两种上下文的概念和使用。
2.5.1界面上下文(Activity Context)
界面上下文(Activity Context)在应用界面(Activity)启动的时候被创建,主要用于保存对当前界面资源的引用。界面上下文在Activity界面控制器类中被使用,当我们需要加载或者访问Activity相关的资源时,会需要用到该Activity的上下文对象。比如,我们需要在界面中创建一个控件,示例代码如清单2-10所示。
代码清单2-10
public class TestActivity extends Activity {
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView mTextView = new TextView(this);
label.setText("Test Text View");
setContentView(mTextView);
}
...
}
通过上面的代码片断,我们创建了一个文本框控件(TextView),并赋予该控件对应界面控制器(TestActivity)的上下文对象(this)。实际上,把界面控制器的上下文对象传递给控件,就意味着该控件拥有一个指向该界面对象的引用,可以引用界面对象占有的资源;同时,Android界面系统也将该控件绑定到该上下文指向的界面对象,最终组合并展示出来。
界面上下文(Activity Context)的生命周期跟Activity界面的是同步的,即当Activity被销毁的时候,其对应的上下文也被销毁了,同时,和该上下文有关的控件对象也将被销毁并回收。因此,我们也可以认为上下文可以用于串联Android应用之中的对象和组件,在理解了这点之后,在使用上下文的时候就不会迷惑了。此外,Context类中比较常用的方法如下。
getApplicationContext:获取当前应用的上下文对象,相关内容请参考2.5.2节。
getApplicationInfo:获取当前应用的完整信息并存于ApplicationInfo对象中,其中常用的信息包括包名packageName、图标icon以及权限permission等属性,更多属性可参考SDK中android.content.pm.ApplicationInfo类的说明。
getContentResolver:获取ContentResolver对象,用于查询所需的Content Provider提供的信息,更多知识请参考2.4.4节内容。
getPackageManager:获取PackageManager对象,PackageManager的用途比ApplicationInfo更加广泛,该类可以从系统的PackageManagerService中获取安装包和运行进程的信息,作用于系统范围。
getPackageName:获取包名,包名(packageName)可作为Android应用的唯一标识。
getResources:获取应用的资源对象Resources,该对象提供一系列的get方法来获取图形Drawable、字符串String以及视频Movie等资源。
getSharedPreferences:获取用于持久化存储的SharedPreferences对象,相关内容请参考2.6.1节。
getSystemService:获取系统级别服务的对象,Android应用框架为我们提供了丰富的系统服务,getSystemService方法就是用于获取这些系统服务对象并运用到应用开发中去。表2-4中列出了常用系统服务及其简单介绍,大家可以先了解一下。
表2-4 Android常用系统服务
服务名返回对象服务功能
ACTIVITY_SERVICEActivityManager系统应用程序管理
ALARM_SERVICEAlarmManager系统闹钟服务
CONNECTIVITY_SERVICEConnectivity网络连接服务
KEYGUARD_SERVICEKeyguardManager键盘锁服务
LAYOUT_INFLATER_SERVICELayoutInflater获取Xml模板中View组件服务
LOCATION_SERVICELocationManager位置服务,如GPS等
NOTIFICATION_SERVICENotificationManager状态栏和通知栏服务
POWER_SERVICEPowerManager系统电源管理
SEARCH_SERVICESearchManager系统搜索服务
TELEPHONY_SERVICETelephonyManager系统电话服务
VIBRATOR_SERVICEVibrator手机震动服务
WIFI_SERVICEWifiManager手机WIFI相关服务
WINDOW_SERVICEWindowManager系统窗口管理
界面上下文是Android应用开发中最经常被使用的上下文对象,应用界面中几乎所有的UI控件都需要用到,这一点在实际运用的过程中大家会体会得更深刻。
2.5.2应用上下文(Application Context)
应用上下文(Application Context)在整个应用(Application)开始的时候被创建,用于保存对整个应用资源的引用,在程序中可以通过界面上下文的getApplicationContext方法或者getApplication方法来获取。在实际应用的时候,我们通常会把应用上下文当做全局对象的引用来使用。当然,对于不同的应用我们会定义应用对象来使用,如代码清单2-11所示。
代码清单2-11
class TestApp extends Application {
...
private String status;
public String getStatus(){
return status;
}
public void setStatus(String s){
status = s;
}
...
}
TestApp应用类继承自Application基类,定义了自己的状态变量和get/set方法,可在整个应用程序中进行设置和获取。当然,我们还需要在应用程序的配置文件AndroidManifest.xml中进行配置,如代码清单2-12所示。
代码清单2-12
<application android:name=".TestApp"
android:icon="@drawable/icon"
android:label="@string/app_name">
…
</application>
配置完毕之后,在应用程序的Activity界面中就可以使用getApplicationContext来获取该应用的上下文对象来完成所需功能了,使用范例请参考代码清单2-13。
代码清单2-13
class TestActivity extends Activity {
...
@Override
public void onCreate(Bundle b){
...
TestApp app = (TestApp) this.getApplicationContext();
String status = app.getStatus();
...
}
...
}
实际上,在Android应用框架中,android.app.Activity类和android.app.Application类都是从android.content.Context类继承而来的,这也是为什么可以在Activity和Application中方便地使用this来代替对应上下文的原因。当然,理解两种Android上下文的用法在Android应用编程中是非常重要的,因为只有理解了Android上下文才能比较完整地理解Android应用的运行环境,进而更好地控制应用的运行状态。另外,我们也会在第7章中通过实例来加深大家对Android上下文用法的理解。
2.6Android数据存储
前面刚介绍过上下文对象的使用,其最重要的功能之一,就是用于存储应用运行期间产生的中间数据。接下来,我们来讨论Android应用中持久化类型数据的存储方案。对于移动互联网应用来说,我们经常把核心数据存储在服务端,也就是我们常说的“云端”,但是在实际项目中也会经常使用到Android系统内部的数据存储方案,接下来让我们认识一下几种最常用的数据存储方案。
2.6.1应用配置(Shared Preferences)
在Android系统中,系统配置(Shared Preferences)是一种轻量级的数据存储策略,只能用于存储key-value格式的数据(类似于ini格式),因此这个特点也决定了我们不可能在其中存储其他各种复杂格式的数据。由于系统配置使用起来比较简单方便,所以我们经常用它来存储一些类似于应用配置形式的信息。代码清单2-14就是一个简单的例子。
代码清单2-14
...
settings = getPreferences(Context.MODE_PRIVATE);
if (settings.getString("username", null) == null) {
SharedPreferences.Editor editor = settings.edit();
editor.putString("username", "james");
editor.commit();
}
...
以上代码的逻辑很简单:先检查是否存在“username”的值,若不存在则保存“james”字符串为“username”。这里我们重点分析两点:首先是关于Context.MODE_PRIVATE,MODE_PRIVATE代表此时Shared Preferences存储的数据是仅供应用内部访问的,除此之外,Android系统中还提供MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE两种模式,分别用于表示数据是否允许其他应用来读或者写;另外还需要注意的一点是,我们在操作数据的时候必须使用SharedPreferences.Editor接口来编辑和保存数据,最后还必须调用commit方法进行提交,否则数据将不会被保存。
另外,系统配置信息会被存储在“/data/data”下对应的应用包名下的shared_prefs目录里,一般是以XML文件格式来存储的。在Eclipse中,我们可以使用DDMS工具(本章的2.10.3节会介绍)打开对应的目录进行查看。
2.6.2 本地文件(Files)
将数据保存成为文件应该是所有系统都会提供的一种比较简单的数据保存方法,我们已经知道Android系统是基于Linux系统来开发的,而Linux系统就是一个文件系统,很多的数据都是以文件形式存在的。与系统配置不同,文件可存储的格式是没有限制的,所以使用范围自然也比系统配置广得多,除了可用于各种类型文件的读写,我们还经常用于保存一些二进制的缓存数据,比如图片等。
在Android中,我们一般使用openFileOutput方法来打开一个文件,此方法会返回一个FileInputStream对象,然后我们就可以选择使用合适的方法来操作数据。比如,对于cfg或者ini类型的文件来说,我们可以使用Properties的load方法来直接载入;对于其他普通的文件,我们则可以使用InputStreamReader和BufferedReader来读取。代码清单2-15就是一个典型的在Android系统中读取文件内容的例子。
代码清单2-15
...
public String getFileContent (String filePath) {
StringBuffer sb = new StringBuffer();
FileInputStream stream = null;
try {
stream = this.openFileInput(filePath);
BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (...) {
...
} finally {
if (stream != null) {
try {
stream.close();
} catch (...) {
...
}
}
}
return sb.toString();
}
...
在上面的代码中,我们实现了一个名为getFileContent的方法,用于获取对应文件的内容;其中就使用了openFileInput来获取文件数据,并通过一系列的拼装,最终返回整个文件的内容。另外,我们需要了解一下,在Android系统中,文件一般会存储到和配置文件同级的目录下,只不过目录名不是shared_prefs,而是files。更多关于Android文件存储的例子我们会在本书第7章中进行详细介绍。
2.6.3数据库(SQLite)
关于数据库的概念,我相信大家都已经非常熟悉了。Android系统给我们提供了一个强大的文本数据库,即SQLite数据库。它提供了与市面上的主流数据库(如MySQL、SQLServer等)类似的几乎所有的功能,包括事务(Transaction)。由于篇幅限制,我们不能在这里介绍太多关于SQLite数据库的内容,因此,如果大家想了解更多信息请到SQLite的官方网站(http://www.sqlite.org)查看。
与之前介绍的两种数据存储模式不同,数据库的存储方式偏向于存取的细节,比如,我们可以把同一类型的数据字段定义好,并保存到统一的数据表中去,进而可以针对每个数据进行更细节的处理。所以,如果可能的话,尽量使用数据库来存储数据,这样会大大增强应用的结构性和扩展性。另外,我们还经常把SQLite数据库和前面所提到的Android四大组件之一的“数据提供者”结合使用,因为它们对于“增删查改”接口的定义和使用实际上是一致的。另外,我们在使用的过程中经常通过继承SQLiteOpenHelper类并实现其中的抽象方法的形式来构造基础的DB操作类,使用范例如代码清单2-16所示。
代码清单2-16
...
public class DBHelper extends SQLiteOpenHelper {
/* 数据库配置 */
private static final int DB_VERSION = 1;
private static final String DB_NAME = "mydb.db";
private static final String DB_TABLE = "mytable";
/* 数据库初始化和更新SQL */
private static final String SQL_CREATE = "CREATE TABLE ...";
private static final String SQL_DELETE = "DROP TABLE ...";
/* 构造函数 */
public DBHelper(Context context){
super(context, DB_NAME, null, DB_VERSION);
}
/* 初始化数据库 */
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE);
}
/* 升级数据库 */
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DELETE);
}
}
...
此外,在需要使用的时候,我们可以通过getReadableDatabase和getWritableDatabase来获取数据库句柄分别进行读和写的操作。另外,数据库文件会被存在shared_prefs和files的同级目录下,目录名为databases。关于SQLite数据库的更多用法,我们也会在第7章中结合具体实例做进一步的介绍。
2.7Android应用界面
Android应用界面系统,即Android UI(User Interface)系统是Android应用框架最核心的内容之一,也是开发者们需要重点掌握的内容。如果我们把Android应用也分为前后端两部分的话,那么之前介绍的核心要点和四大组件等都属于后端,而Android UI系统则属于前端。后端保证应用的稳定运行,而前端则决定应用的外观和体验。对于一个优秀的Android应用来说,漂亮的外观和流畅的体验是必不可少的。接下来,我们便来学习Android外观系统的知识。
在2.3.3节中我们已经简单介绍了Android应用框架中的外观系统(View System),也就是Android UI系统的基础知识。我们知道了对于Android应用来说,最重要的两个基础类就是View和ViewGroup:View是绝大部分UI组件的基础类,而ViewGroup则是所有Layout布局组件的基类。当然,ViewGroup也是View的子类。相关类库的树形结构如下。
java.lang.Object
- android.view.View
- android.view.ViewGroup
- android.widget.FrameLayout
- android.widget.LinearLayout
- android.widget.TableLayout
- android.widget.RelativeLayout
- android.widget.AbsoluteLayout
本节将重点介绍Android应用(非游戏)使用的UI系统。一般来说,我们都使用XML格式的模板文件来书写对应的UI界面,当然,这种做法也比较符合MVC的设计思想。另外,由于UI模板独立于逻辑之外,界面设计师们就可以更加专注于他们自己的事情。在模板文件中,每个UI控件都由对应的XML标签来表示,具体的控件标签见表2-3,大家可以回顾一下。
2.7.1控件属性
我们知道Android UI系统给我们提供了丰富多彩的控件,比如TextView、Button、TextView、EditText、ListView、CheckBox、RadioButton等,具体如表2-3所示。我们可以使用这些不同功能的控件来完成各种各样用户界面的需求。那么控件本身的属性应该如何设置呢?实际上,每个UI控件都有很多的属性可供我们选择,我们一般都是通过设置这些属性来设置UI控件的外观、位置等。代码清单2-17中就是使用XML来表示文本框控件(TextView)的示例,实际的显示效果是在整个UI界面的左上方打印一段文字“I am a TextView”。
代码清单2-17
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a TextView" />
Android UI控件使用android:layout_width和android:layout_height属性控制其宽度和高度,属性值为wrap_content表示元素的外观由内容大小决定,而fill_parent则表示元素大小由外层的元素决定。我们经常使用fill_parent来实现自适应的界面布局,因为最外层的元素必然就是手机屏幕。此外我们需要注意的是,这两个属性是每个UI控件必须指定的。
另外,Android UI控件的外观采用类似于CSS标准的“盒子模型”,也有margin和padding的概念,元素内边距使用android:padding来表示,外边距则采用android:layout_margin来控制。这两个属性也是我们最常使用的“利器”之一,用其可使整个界面各个控件之间的间隔更为合理、美观。Android UI控件“盒子模型”如图2-7所示,大家可以结合示意图理解一下。
最后,我们来学习一些基础的Android UI控件属性,这些属性在UI组件基础类View类中定义,具备很强的通用性,可被绝大部分的UI控件所使用,因此也被称作“通用属性”。对于我们来说,只有掌握了这些通用属性的用法,才能够更好地控制UI组件并运用它们组装出各种各样的UI界面。
android:id:每个UI控件的代表性id。我们经常在程序中使用findViewById方法来选取对应id的控件,然后再对该控件进行属性控制或者事件处理,用法和HTML元素标签属性中的id类似。
android:background:控件背景,可以是颜色值,也可以是图像或者Drawable资源等,如果值为@null,则表示透明背景。
android:layout_width:UI控件的宽度,常见属性有fill_parent、wrap_parent等。前面我们已经简单介绍过这个属性的用法,它是每个控件必须具备的属性之一。
android:layout_height:UI控件的高度,常见属性和用法和宽度一样,也是每个控件必须具备的属性之一。
android:layout_gravity:用于控制UI控件相对于其外层控件的位置,其属性值就代表其位置,如顶部(top)、底部(bottom)、左边(left)、右侧(right)、垂直居中(center_vertical)、水平居中(center_horizontal)、绝对居中(center)、垂直填满(fill_vertical)、水平填满(fill_horizontal)、完全填满(fill)等。另外,这些属性可以并列存在,我们常使用“”符号隔开,如“center_verticalcenter_horizontal”表示垂直水平居中。
android:layout_margin:UI控件的外边距,使用方式见图2-7 所示的“盒子模型”。
android:padding:UI控件的内边距,使用方式见图2-7 所示的“盒子模型”。
android:gravity:控件内部的元素相对于控件本身的位置,其属性值和使用方法与android:layout_gravity基本一致。
android:visibility:显示或隐藏控件,控件默认是显示状态的。
通用属性常用于操控UI控件的外观和位置,通常能对UI界面的构建起到很大的作用。当然,除了通用属性之外,不同的UI控件还会有各自专属的“控件属性”,这些属性我们将在后面讲到各种UI控件的概念和用法时详细介绍,具体内容可参考第7章中与界面控件相关的章节内容。
2.7.2布局(Layout)
Android UI系统中的布局文件其实和HTML有点类似,都是用XML标签所代表的各种UI控件组合或者嵌套而成的,只不过,Android模板文件的格式比HTML更严谨些,属性也更复杂些。在Android UI界面设计中,Layout布局控件就像“建筑师”一样,帮助我们把整个界面的框架布局搭建起来,并把每个控件都放到合适的位置上。我们最经常使用的布局有以下几种,我们来逐个介绍一下。
1. 基本布局(FrameLayout)
基本布局(FrameLayout)是所有Android 布局中最基本的,此布局实际上只能算是一个“容器”,里面所有的元素都不能被指定位置,默认会被堆放到此布局的左上角。此布局在普通的应用中用得不是很多,但是因为简单高效,所以在一些游戏应用中还是经常被用到。
2. 线性布局(LinearLayout)
线性布局(LinearLayout)是应用开发中最常用的布局之一,分为横向和纵向两种,由android:orientation属性来控制。当属性值为“horizontal”时表示横向的线性布局,常用于并排元素的界面;而“vertical”则表示纵向也就是垂直的线性布局,它的用处更广,普通应用中的大部分界面都是垂直排列的,比如列表界面、配置界面等。
线性布局的用法很简单,就拿垂直的线性布局来说,我们只要把所需的控件按照顺序放到布局标签中间就可以了,Android UI系统会自动按照从上到下的顺序展示出来。代码清单2-18就是一个简单的代码示例,其功能很简单,就是把一个TextView和Button垂直并排在这个线性布局中。大家在阅读示例代码的同时可以顺便复习一下UI控件属性的用法。
代码清单2-18
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView android:id="@+id/text_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a TextView" />
<Button android:id="@+id/button_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a Button" />
</LinearLayout>
3. 相对布局(RelativeLayout)
相对布局(RelativeLayout)也是最常使用的布局之一,由于其内部的所有元素都是按照相对位置来排列的,所以不需要嵌套其他的布局,它可以使UI模板布局更加地简洁和高效。该布局中的控件元素都是以“参照控件”为准来排布的,比如控件属性设置为“android:layout_toRightOf="@+id/referBox"”,则表示该控件位于id为referBox的参照控件的右边。以下是相对布局中其他常用属性的列表,供大家参考。
android:layout_toLeftOf:该控件位于参照控件的左方。
android:layout_toRightOf:该控件位于参照控件的右方。
android:layout_above:该控件位于参照控件的上方。
android:layout_below:该控件位于参照控件的下方。
android:layout_alignParentLeft:该控件是否与父组件的左端对齐。
android:layout_alignParentRight:该控件是否与父组件的右端对齐。
android:layout_alignParentTop:该控件是否与父组件的顶部对齐。
android:layout_alignParentBottom:该控件是否与父组件的底部对齐。
android:layout_centerInParent:该控件是否与父组件居中对齐。
android:layout_centerHorizontal:该控件是否与父组件横向居中对齐。
android:layout_centerVertical:该控件是否与父组件垂直居中对齐。
4. 绝对布局(AbsoluteLayout)
绝对布局(AbsoluteLayout)的用法类似于HTML中的层属性“position=absolute”,该布局内部的控件可以使用android:layout_x和android:layout_y两个属性来指定它相对于布局坐标轴原点的X轴和Y轴方向的距离。图2-8就是绝对布局的示意图。
图2-8绝对布局的示意图
5. 表格布局(TableLayout)
大家如果熟悉HTML的话,应该非常熟悉表格布局(TableLayout),像一些表格型的信息列表都是使用表格布局来展示的。表格型布局的标签有两个—<TableLayout/>和<TableRow/>,前者是表格布局的主要标签,整个表格布局的框架,类似于HTML标签中的<table/>;后者是表格行,类似于HTML标签中的<th/>或者<tr/>。表格布局的使用范例如代码清单2-19所示。
代码清单2-19
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="0,1,2">
<TableRow>
<TextView android:gravity="center" android:text="ID"/>
<TextView android:gravity="center" android:text="NAME"/>
</TableRow>
<TableRow>
<TextView android:gravity="center" android:text="1"/>
<TextView android:gravity="center" android:text="james"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Edit"/>
</TableRow>
<TableRow>
<TextView android:gravity="center" android:text="2"/>
<TextView android:gravity="center" android:text="iris"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Edit"/>
</TableRow>
</TableLayout>
上述XML模板的最终显示结果如图2-9所示,我们可以看到,这是一个3行3列的标准表格结构,对应到代码中就是3个<TableRow/>标签,每个标签中包含3个控件。
图2-9表格布局示例
另外,我们还要注意的是,<TableLayout/>有3个很重要的属性:android:stretchColumns、android:shrinkColumns和android:collapseColumns,分别对应的是拉伸、收缩和隐藏列行为,如代码清单2-19中我们使用“android:stretchColumns="0,1,2"”,就表示所有列都是拉伸状态,因此每列中的控件才会平分并填满整行的空间;假如我们设置“android:collapseColumns="2"”,那么最右边的列将会被隐藏。
6. 标签布局(TabLayout)
标签布局(TabLayout)在移动应用中是相当流行的,其用法相对比其他布局复杂一些,需要配合程序来实现。接下来我们来看一个简单的标签布局的实例,其模板文件如代码清单2-20所示。
代码清单2-20
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<TabWidget android:id="@android:id/tabtitle_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<FrameLayout android:id="@android:id/tabcontent_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
</TabHost>
此界面最外面是一个<TabHost/>标签,里面嵌套了一个垂直的线性布局,该线形布局里面又包含了一个<TabWidget/>和<FrameLayout/>,这些标签都是需要在程序中设置的。紧接着,在模板对应的Activity类中设置该TabLayout的逻辑,使用范例见代码清单2-21。
代码清单2-21
public class TabDemo extends TabActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 初始化资源对象
Resources res = getResources();
TabHost tabHost = getTabHost();
TabHost.TabSpec ts;
Intent intent;
// 设置第1个Tab
intent = new Intent().setClass(this, Tab1Activity.class);
ts = tabHost
.newTabSpec("tab1")
.setIndicator("Tab1", res.getDrawable(R.drawable.ic_tab_1))
.setContent(intent);
tabHost.addTab(ts);
// 设置第2个Tab
intent = new Intent().setClass(this, Tab2Activity.class);
ts = tabHost
.newTabSpec("tab2")
.setIndicator("Tab2", res.getDrawable(R.drawable.ic_tab_2))
.setContent(intent);
tabHost.addTab(ts);
// 设置第3个Tab
intent = new Intent().setClass(this, Tab3Activity.class);
ts = tabHost
.newTabSpec("tab3")
.setIndicator("Tab3", res.getDrawable(R.drawable.ic_tab_3))
.setContent(intent);
tabHost.addTab(ts);
// 设置默认选中Tab
tabHost.setCurrentTab(0);
}
...
}
我们从代码注释中可以清楚地看到,在该实例中我们添加了3个Tab标签,程序使用newTabSpec方法获取TabHost.TabSpec对象,然后使用setIndicator和setContent方法设置Tab的顶部样式和内部信息,最后再调用setCurrentTab方法设置默认选中的标签页。最后,分别实现Tab1Activity、Tab2Activity和Tab3Activity界面类的逻辑,并加入声明到Manifest应用的配置文件中。至此,整个标签布局的设置就完成了。
2.7.3事件(Event)
了解完UI控件和界面布局的基本知识之后,我们还需要知道如何控制这些界面上的控件元素。Android应用框架为我们提供了事件机制来处理用户触发的动作,常见的事件包括键盘事件KeyEvent、输入事件InputEvent、触屏事件MotionEvent等。在实际应用中,我们需要掌握如何响应当用户操作这些控件时所触发的事件。比如,用户点击某个按钮控件(Button)之后需要执行一些程序逻辑,此时我们需要使用Android系统给我们提供的事件监听器Listener来捕获按钮的点击事件来执行这些逻辑。本节中我们将会介绍Android应用框架中比较常见的监听器。
1. View.OnClickListener事件
View.OnClickListener是最经常使用的监听器之一,用于处理点击事件。其实,该类也是View基类中的公用接口,其接口方法为onClick(View v)。方法只有一个参数,就是点击事件触发的控件对象的本身。我们在使用过程中必须实现onClick方法,也就是把点击之后需要处理的逻辑代码放到此方法中。代码清单2-22就是相关的使用范例。
代码清单2-22
btnObj = (Button) this.findViewById(R.id.demo_btn_id);
btnObj.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 按钮点击之后的动作
...
}
});
上面的代码实现的就是id为demo_btn_id的按钮控件的点击事件,我们在使用findViewById获取到按钮实例对象之后,又通过setOnClickListener方法设置View.OnClickListener监听器对象的实现,点击事件需要处理的逻辑我们会在onClick方法中实现。大家可以看到,Android UI事件的概念和用法与JavaScript语言有点类似。
2. View.OnFocusChangeListener事件
监听器View.OnFocusChangeListener用于处理选中事件,比如界面中有若干个UI控件,当需要根据选中不同的控件来处理不同的逻辑时,就可以使用按钮控件对象的setOnFocusChangeListener方法来设置View.OnClickListener监听器对象。选中需要处理的逻辑会在该监听器对象的onFocusChange方法中实现。
onFocusChange方法有两个参数:第一个是事件触发的控件对象,我们可以用其判断并处理不同控件的触发事件,另一个则是布尔型的值,表示改控件对象的最新状态。另外,监听器的具体用法和View.OnClickListener类似。
3. View.OnKeyListener事件
监听器View.OnKeyListener用于处理键盘的按键。我们可以在该监听器的onKey方法中处理用户点击不同按键时所需要处理的逻辑。在Android的键盘系统中,每个按键都有自己的代码,也就是keyCode。需要注意的是,onKey方法的第二个参数传递的就是用户点击按键的keyCode,而后我们就可以使用switch语句来处理不同的按键事件了。这个思路其实和JavaScript中的onkey系列方法非常类似,读者如果熟悉JavaScript的话,可以对照着学习一下。
4. View.OnTouchListener事件
监听器View.OnTouchListener用于处理Android系统的触屏事件。如果我们需要对一些触摸动作做处理,或者需要处理比点击动作此类动作更细粒度的动作的话,就要用到这个监听器了。此监听器必须实现的接口方法是onTouch(View v, MotionEvent event),我们需要注意的是第二个参数,因为这个参数表示的是用户触发的动作事件,我们可以根据这个参数的值来处理比较复杂的手势(gesture)动作。
MotionEvent事件中比较常见的动作和手势常量的说明如下,供大家参考。
ACTION_DOWN:按下手势,包含用户按下时的位置信息。
ACTION_UP:松开手势,包含用户离开时的位置信息。
ACTION_MOVE:拖动手势,包含最新的移动位置。
ACTION_CANCEL:结束手势,类似于ACTION_UP,但是不包含任何位置信息。
ACTION_OUTSIDE:离开控件元素时所触发的事件,只包含初始的位置信息。
EDGE_BOTTOM:碰触屏幕底部时所触发的事件。
EDGE_LEFT:碰触屏幕左边时所触发的事件。
EDGE_RIGHT:碰触屏幕右边时所触发的事件。
EDGE_TOP:碰触屏幕顶部时所触发的事件。
ACTION_MASK:多点触碰事件的标志,可用于处理多点触摸事件。
ACTION_POINTER_DOWN:第二点按下时的触发事件。
ACTION_POINTER_UP:第二点松开时的触发事件。
可以想象,如果缺少事件响应的支持,Android应用的界面将会变得毫无交互性。因此,学会使用UI控件的各种响应事件的用法对于Android应用开发来说是非常重要的。通常情况下,我们会使用不同的事件来让界面中的元素生动起来。比如,我们可以通过实现某个UI控件的View.OnClickListener事件来响应用户的点击动作(如代码清单2-22所示),或者还可以使用View.OnTouchListener事件来响应一些更加复杂的动作。
2.7.4菜单(Menu)
菜单是Android应用系统中最有特色的功能之一,也是每个Android应用必不可少的组件之一。合理地使用菜单不仅可以帮助我们节省界面空间,还可以提升用户的操作体验。一般,我们最常用的菜单有以下3种,下面我们分别来学习一下。
1. 选项菜单(Options Menu)
选项菜单(Options Menu)是Android应用中最经常被使用的菜单,当用户按下系统菜单键时出现。在Activity中,我们通常使用onCreateOptionsMenu方法来初始化菜单项,然后再使用onOptionsItemSelected方法处理每个菜单项选中时的逻辑。使用范例如代码清单2-23所示。
代码清单2-23
public class MenuActivity extends Activity {
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// 添加书写按钮菜单项
menu.add(0, MENU_APP_WRITE, 0, R.string.menu_app_write).setIcon(...);
// 添加注销按钮菜单项
menu.add(0, MENU_APP_LOGOUT, 0, R.string.menu_app_logout).setIcon(...);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_APP_WRITE:
// 点击书写菜单项之后的逻辑
...
break;
case MENU_APP_LOGOUT:
// 点击注销菜单项之后的逻辑
...
break;
}
return super.onOptionsItemSelected(item);
}
}
当然,如果我们需要添加一些每次菜单加载时都需要执行的逻辑,则需要使用onPrepareOptionsMenu方法来处理,因为onCreateOptionsMenu只在菜单项初始化的时候执行一次。
2. 上下文菜单(Context Menu)
上下文菜单(Context Menu)的概念和PC上应用软件的快捷菜单有点类似,在UI控件注册了此菜单对象以后,长按视图控件(2秒以上)就可以唤醒上下文菜单。在Activity类中,我们可以使用onCreateContextMenu方法来初始化上下文菜单。和选项菜单略微不同的是,此方法在每次菜单展示的时候都会被调用。另外,处理上下文菜单点击事件的方法名为onContextItemSelected,其用法和前面介绍的选项菜单中的onOptionsItemSelected方法类似。
3. 子菜单(Submenu)
在Android应用中点击子菜单时会弹出悬浮窗口显示子菜单项,子菜单(Submenu)可以被添加到其他的菜单中去。使用方法也很简单,Submenu的使用范例如代码清单2-24所示。我们需要注意的是,子菜单是不可以嵌套的,即子菜单中不能再包含其他子菜单,我们在使用的时候必须注意这个问题。
代码清单2-24
publicboolean onCreateOptionsMenu(Menu menu) {
// 初始化变量
int base = Menu.FIRST;
// 添加子菜单
SubMenu subMenu = menu.addSubMenu(base, base+1, Menu.NONE, "子菜单-1");
// 设置图标
subMenu.setIcon(R.drawable.settings);
// 添加子菜单项
subMenu.add(base, base+1, base+1, "子菜单项-1");
subMenu.add(base, base+2, base+2, "子菜单项-2");
subMenu.add(base, base+3, base+3, "子菜单项-3");
return true;
}
以上我们介绍了Android系统中最常见的几种菜单的概念和基本用法,关于菜单组件实际运用的更多信息,我们将在实战篇的7.5.1节中结合实际案例做进一步的介绍。
2.7.5主题(Theme)
为了让Android UI界面开发更加快速方便,同时具有更好的复用性,应用框架为我们提供了样式(style)和主题(theme)两个功能。这两个功能让我们可以更好地控制UI界面的外观,并可以实现一些更高级的功能,比如换肤功能等。
首先,需要了解的是,我们通常会把样式和主题的声明放在Android应用框架的资源目录res/values/下的styles.xml文件中,使用范例如代码清单2-25所示。
代码清单2-25
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CommonText" parent="@style/Text">
<item name="android:textSize">12px</item>
<item name="android:textColor">#008</item>
</style>
</resources>
我们可以看到,在这个样式文件中我们声明了一个名为“CommonText”的样式,里面包含了该样式的两个属性:字体大小“android:textSize”和字体颜色“android:textColor”属性。另外,样式是支持继承的,比如,该样式就继承自系统的基础“Text”样式,这种使用parent属性设置父样式的用法还是比较容易理解的。了解完样式和主题的写法,接下来让我们认识一下样式和主题之间的区别。
1. 样式(style)
Android的UI系统中,样式(style)的概念和CSS中样式的概念非常类似,我们可以把一些常用的样式提取出来,比如代码清单2-20中,我们就把一种常见的文字样式提取出来并保存为“CommonText”的样式。应用样式的时候,我们只需要在对应控件的声明中加上“style="@style/CommonText"”属性值即可。一般来说,样式都只会被应用于单个View控件中。
2. 主题(theme)
与样式不同,主题(theme)一般被用于更外层的ViewGroup控件中,比如,我们需要让Activity下所有控件的字体都用CommonText的样式,那么我们就可以在应用配置文件中的<activity/>标签加上“android:theme="CommonText"”的属性。但是,如果我们把样式用在ViewGroup上,对于ViewGroup之下的其他View控件却是没有影响的。另外,Android系统还定义了几个基本的系统主题供我们使用,比如Theme.Light主题就是以亮色背景为基调的主题样式。
学会灵活使用样式和主题来渲染Android应用的UI界面是非常重要的,因为该技术不仅可以让界面设计更加容易,还可以简化模板文件的代码,减少开发成本。因此,在实践的过程中,我们要有意识地去运用这些知识和技巧,逐渐掌握Android UI系统的使用。
2.7.6对话框(Dialog)
在Android应用界面中,经常需要弹出一些悬浮于底层UI界面之上的操作窗口。当这种窗口显示的时候,底层界面通常会被半透明层所覆盖住,焦点则会被该窗口获得,这种窗口就被称为对话框,或者是Dialog。应用中常用的Dialog有提示对话框(AlertDialog)、进度对话框(ProgressDialog)、日期选择对话框(DatePickerDialog)以及时间选择对话框(TimePickerDialog)等。在本节中,我们将重点介绍其中较常使用的两种Dialog的用法。
1. 提示对话框(AlertDialog)
提示对话框(AlertDialog)可以算是Android应用中最经常使用的对话框控件了,其主要用于显示提示信息,当然,可以加上确认和取消(YES和NO)按钮。创建AlertDialog需要使用AlertDialog.Builder子类,代码清单2-26演示了创建AlertDialog对话框的标准过程。
代码清单2-26
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to exit?")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
MyActivity.this.finish();
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
在以上代码中,首先使用AlertDialog.Builder(Context)方法来获取Builder对象,然后使用Builder类提供的公用方法来设置AlertDialog的文字和属性,接着使用该类的create方法来创建AlertDialog对象,最后调用show方法展示该对话框。显示效果如图2-10所示。
2. 进度对话框(ProgressDialog)
进度对话框(ProgressDialog)在Android应用开发中也经常会用到,主要用于在耗时操作等待时显示。其用法比较简单,一般情况下,只需要调用ProgressDialog的show方法即可,如代码清单2-27所示。
代码清单2-27
…
ProgressDialog dialog = ProgressDialog.show(this, "", "Loading. Please wait...", true);
…
以上代码创建了一个最基本的进度对话框,显示效果如图2-11所示。
当然,ProgressDialog类还提供了丰富的对话框属性设置方法,如设置进度条的样式、标题、提示信息,以及是否显示按钮等。更多用法示例可参考后面7.11.3节中的内容。至于其他对话框的用法由于篇幅原因,这里不做详细介绍。
2.8Android图形界面
前面介绍了Android应用界面(Android UI)的相关内容,不过对于一些游戏应用来说,这些UI控件往往派不上用场。此外,一些特殊的Android应用也有可能会使用到比较底层的图形类库,因此,本节我们就来学习Android的图形系统。
Android系统中的图形大致可以分为2D图形和3D图形两类,2D图形的类库在android.graphics包下,本节将会重点介绍;3D图形的类库在android.opengl包下,由于这部分内容和游戏开发关系比较紧密,这部分内容将被放在本书第13章中介绍,感兴趣的朋友可以提前参考13.1.4节中的内容。
2.8.1画笔(Paint)
首先,让我们来想象一下,当我们绘画的时候,最重要的两样东西是什么?答案应该没有什么悬念,那就是画笔和画布。实际上,在Android系统中绘制图形的原理是相同的,我们同样需要先使用程序构造一把画笔(Paint),然后在画布(Canvas)上进行绘画。
Android系统中的画笔类,即android.graphics包下的Paint类,该类包含了一系列的方法与属性,用于构造绘制图形用的画笔。我们把常用的方法归纳到表2-5中。
表2-5画笔类常用方法
方法名说明
setARGB(int a, int r, int g, int b)设置画笔透明度以及RGB颜色
setAlpha(int a)设置画笔透明度
setAntiAlias(boolean aa)设置抗锯齿效果
setColor(int color)设置画笔颜色
setLinearText(boolean linearText)设置线性文本
setPathEffect(PathEffect effect)设置路径效果
setShader(Shader shader)设置阴影效果
setStyle(Paint.Style style)设置画笔样式
setTextScaleX(float scaleX)设置文本缩放效果
setTextSize(float textSize)设置字体大小
以上方法常用于画笔初始化的配置逻辑中,接下来让我们来学习Paint画笔类的使用范例,如参考代码清单2-28所示。
代码清单2-28
public class TestPaintView extends View {
...
private Paint mPaint = new Paint();
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画笔
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setAlpha(200);
mPaint.setStyle(Paint.Style.FILL);
// 绘制矩形
canvas.drawRect(100, 100, 150, 150, mPaint);
}
...
}
以上视图类TestPaintView继承自View基类,主要的绘制逻辑在onDraw方法中,即使用定制好的实心画笔绘制一个红色的矩形,这里我们可以学习到使用Paint画笔类的正确方法。此外,我们还需要注意,这里在使用setColor方法设置画笔颜色的时候,用到了Color类的预定义颜色常量,我们将这些常用的颜色常量归纳到表2-6中。
表2-6画笔类颜色常量
常量名说明
Color.BLACK黑色
Color.BLUE蓝色
Color.CYAN青绿色
Color.DKGRAY灰黑色
Color.GRAY灰色
Color.GREEN绿色
Color.LTGRAY浅灰色
Color.MAGENTA红紫色
Color.RED红色
Color.TRANSPARENT透明
Color.WHITE白色
Color.YELLOW黄色
2.8.2画布(Canvas)
设置好画笔和颜色,就可以开始在画布上绘画了,这时我们就需要用到画布类,即Canvas类。该类包含了一系列的方法与属性,用于设置画布的外观,我们把常用的方法归纳到表2-7中。
Canvas类中常用绘制方法的用法比较简单,Android系统已经在View类的onDraw方法中默认传入了canvas对象,我们可以根据需要使用不同的draw方法绘制出不同的图形。比如,代码清单2-29中就使用了drawRect方法绘制了一个矩形。
表2-7画布类常用方法
方法名说明
clipRect(int left, int top, int right, int bottom)剪裁画布,即需要绘制的部分
drawARGB(int a, int r, int g, int b)设置整个画布的颜色
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)绘制位图
drawCircle(float cx, float cy, float radius, Paint paint)绘制圆形
drawColor(int color)设置画布背景色
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)绘制线形
drawOval(RectF oval, Paint paint)绘制椭圆
drawPoint(float x, float y, Paint paint)绘制点形
drawRect(float left, float top, float right, float bottom, Paint paint)绘制矩形
drawText(String text, float x, float y, Paint paint)绘制文字
restore()重置画布
rotate(float degrees)旋转画布
save()保存画布
然而,游戏应用的画布中通常不只有一个图形,通常需要对其中的某些图形进行特殊处理,比如旋转、变形等,此时需要先使用save方法来保存画布,图形处理完毕之后再调用restore方法来重置、重绘,使用范例如代码清单2-29所示。
代码清单2-29
public class TestCanvasView extends View {
...
private Paint mPaint = new Paint();
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画布颜色
canvas.drawColor(Color.BLACK);
// 设置画笔
mPaint.setAntiAlias(true);
// 剪裁画布
canvas.clipRect(0, 0, 200, 200);
// 保存画布
canvas.save();
// 绘制一个矩形
canvas.rotate(10.0f);
mPaint.setColor(Color.RED);
canvas.drawRect(100, 100, 150, 150, mPaint);
// 重置画布
canvas.restore();
// 绘制另一个矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(100, 0, 200, 100, mPaint);
}
...
}
以上程序绘制了两个矩形。其中,红色的矩形绕着屏幕左上方的顶点顺时间旋转了10°。这里涉及Canvas画布坐标系的知识,我们将在2.8.3节中介绍。另外,我们还可以学习到如何对Canvas画布进行设置、保存、旋转、重置等一系列的操控过程。学习了以上Paint和Canvas类的编程技巧之后,开发者就可以在Android应用和游戏中方便地绘图了。
2.8.3 基础几何图形
前面我们已经学习了画笔(Paint)和画布(Canvas)的基础知识,接下来我们就可以使用这些工具来画图了。实际上,在前面的代码范例中,我们已经介绍了如何使用Canvas对象的drawRect方法来绘制矩形,但是大家可能还不清楚方法中参数值的含义,因此我们先来熟悉Canvas画布的坐标系,如图2-12所示。
从以上的坐标系示意图中,我们可以看出以下几个要点。其一,Canvas画布的坐标原点位于整张画布的左上方,点坐标为“(0,0)”;其二,屏幕横向的是X轴,纵向的是Y轴,屏幕内的点坐标都是正数;其三,以矩形为例,我们可以看到绘图方法(drawRect)中的left、top、right、bottom等参数的含义,其他方法中的类似参数的含义都可以依此类推。
另外,在使用Canvas进行绘图的时候还要注意,画布是按照程序逻辑的先后顺序进行渲染的,因此底部图形的渲染逻辑放在前面,渲染逻辑在后面的图形则会层层覆盖上去,使用范例请参考代码清单2-30。
代码清单2-30
public class TestGraphicsView extends View {
...
private Paint mPaint = new Paint();
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画布颜色
canvas.drawColor(Color.BLACK);
// 设置画笔
mPaint.setAntiAlias(true);
// 画圆形
mPaint.setColor(Color.YELLOW);
canvas.drawCircle(160, 160, 120, mPaint);
// 画矩形
mPaint.setColor(Color.RED);
canvas.drawRect(80, 80, 240, 240, mPaint);
// 画椭圆
mPaint.setColor(Color.GREEN);
RectF rectf = new RectF();
rectf.left = 90;
rectf.top = 100;
rectf.right = 230;
rectf.bottom = 220;
canvas.drawOval(rectf, mPaint);
// 画多边形
Path path = new Path();
path.moveTo(160, 110);
path.lineTo(160-40, 110+80);
path.lineTo(160+40, 110+80);
path.close();
mPaint.setColor(Color.BLUE);
canvas.drawPath(path, mPaint);
...
}
...
}
在上述代码中,TestGraphicsView类的onDraw方法中依次绘制了圆形、矩形、椭圆和多边形,运行结果如图2-13所示,我们可以很清楚地看到这些基础几何图形的显示效果以及图形渲染的先后顺序。
基础几何图形的绘制是Android图形系统的基础知识。在此基础之上,我们可以把Android UI控件结合到一起,开发出丰富多彩的应用UI界面。当然,我们还可以运用View控件的刷新机制完成一些简单的图形动画,相关内容将在2.8.4节中介绍。
2.8.4常见图形变换
常见的图形变换包括位移、旋转、缩放、倾斜等,其中,位移变换在开发者掌握了画布坐标系等基础概念的情况下,实现起来是比较简单的;然而,旋转、缩放以及倾斜变换则涉及变换矩阵(Matrix)的概念,这里需要特别解释一下。
Android系统中的变换矩阵实际上是一个3×3的矩阵,专门用于控制图形变换,矩阵中的每个数值都有其特定的含义。Android SDK中的Matrix类位于android.graphics包下,我们可以通过setValue方法直接设置旋转矩阵的二维数组,但是这种用法比较难懂,更简单的用法是使用Matrix类提供的方法来控制旋转矩阵,比如setRotate方法就用于设定旋转的角度。代码清单2-31就展示了Matrix类的用法。
代码清单2-31
public class TestImageView extends View implements Runnable {
private Bitmap star = null;
private int starWidth = 0;
private int starHeight = 0;
private float starAngle = 0.0f;
private Matrix starMatrix = new Matrix();
public TestImageView(Context context) {
super(context);
// 加载资源
Resources res = this.getResources();
star = BitmapFactory.decodeResource(res, R.drawable.star);
// 获取原始图片宽高
starWidth = star.getWidth();
starHeight = star.getHeight();
// 开始重绘视图
new Thread(this).start();
}
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 重置旋转矩阵
starMatrix.reset();
// 设置旋转角度
starMatrix.setRotate(starAngle);
// 重绘旋转的图形
Bitmap starBitmap = Bitmap.createBitmap(star, 0, 0, starWidth, starHeight, starMatrix, true);
canvas.drawBitmap(starBitmap, 0, 0, null);
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(100);
starAngle++; // 旋转角度
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 通知主线程更新图像
this.postInvalidate();
}
}
}
上述代码中的TestImageView类是一个完整的重绘画布视图的例子。首先,该类继承自View基类,同时还包含了一个线程类的run方法,在该方法的逻辑中,每100ms进行一次重绘,即调用postInvalidate方法通知主线程更新图像。其次,在TestImageView类的构造方法中,主要包含了资源初始化的逻辑,这里程序加载了一个五星形状的图像资源文件。另外,在onDraw方法中,我们可以看到starMatrix变换矩阵的常见用法之一,即通过setRotate方法设置旋转的角度。该程序最终的运行效果,就是画出了一个绕着屏幕左上方顺时针旋转的五角星,如图2-14所示。
当然,我们还可以让图像绕着某个中心点旋转,这也不是问题,我们只需要对onDraw方法的逻辑稍做修改即可,修改过的逻辑实现如代码清单2-32所示。
代码清单2-32
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 重置旋转矩阵
starMatrix.reset();
// 设置旋转中心
float transX = 100;
float transY = 100;
float pivotX = starWidth/2;
float pivotY = starHeight/2;
starMatrix.setRotate(starAngle, pivotX, pivotY);
starMatrix.postTranslate(transX, transY);
// 重绘旋转的图形
canvas.drawBitmap(star, starMatrix, null);
}
...
要让图形绕着其中心旋转,首先要使用setRotate方法设置图形的旋转中心,然后再使用postTranslate方法把图形平移到相应的位置,即坐标(transX,transY)。该实例的运行效果如图2-15所示,我们可以看到屏幕上出现了一个不断自转的五角星。
当然,除了旋转之外,常见的图形变换还包括大小变换、倾斜变换等,限于篇幅,这里就不做介绍了,有兴趣的读者可以参考Matrix类文档中的preScale、postScale、preSkew、postSkew等方法。这里我们还需要注意的是pre和post系列方法的区别,带有pre前缀的方法表示此变换逻辑需要应用在所有变换逻辑之前,而带有post前缀的方法则表示此变换逻辑会依次往后排列,因此代码清单2-28中的旋转逻辑也可以使用代码清单2-33中的代码替代。
代码清单2-33
...
public void onDraw(Canvas canvas) {
...
starMatrix.setTranslate(transX, transY);
starMatrix.preRotate(starAngle, pivotX, pivotY);
...
}
...
2.9Android动画效果
适当地使用动画效果可以很好地提升Android应用或游戏的操作体验。目前Android系统支持的动画效果主要有两种,即逐帧动画(Frame Animation)和补间动画(Tween Animation)。虽然,在Android 3.0以后的版本中还引入了新的动画系统,但是目前最主流的动画效果还是这两种。
2.9.1逐帧动画(Frame Animation)
逐帧动画类似于GIF动画图片,即按照顺序播放图片。我们通常会在Android项目的res/drawable/目录下面定义逐帧动画的XML模板文件。编码的时候,需要在动画模板文件的<animation-list/>标签中依次放入需要播放的图片,并设置好播放的间隔时间,如代码清单2-34所示。
代码清单2-34
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/a001" android:duration="100"/>
<item android:drawable="@drawable/a002" android:duration="100"/>
<item android:drawable="@drawable/a003" android:duration="100"/>
...
</animation-list>
然后,就可以在Activity界面控制器的逻辑中自由使用了。需要注意的是,逐帧动画并不能独立使用,动画效果的显示还是要借助于ImageView图像控件,简单地说,也就是把动画效果绑定到对应的ImageView图片对象上。假设这里的ImageView元素的ID值,即android:id属性值为img_frame_anim,而之前定义的动画模板文件名为demo_frame_anim.xml,逐帧动画的使用范例如代码清单2-35所示。
代码清单2-35
public class DemoAnimationActivity extends Activity {
ImageView iv;
AnimationDrawable ad;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取对应图片的ImageView对象
iv = (ImageView) findViewById(R.id.img_frame_anim);
// 设置对应图片的背景为动画模板文件
iv.setBackgroundResource(R.drawable.demo_frame_anim);
// 初始化动画对象
ad = (AnimationDrawable) imageView.getBackground();
// 开始动画
ad.start();
}
public void onPause() {
super.onPause();
// 停止动画
ad.stop();
}
...
}
以上代码的逻辑非常简单,我们可以重点关注AnimationDrawable对象的用法,即如何使用start和stop方法控制逐帧动画的播放和停止。
2.9.2补间动画(Tween Animation)
补间动画与逐帧动画在本质上是不同的,逐帧动画通过连续播放图片来模拟动画的效果,而补间动画则是通过在两个关键帧之间补充渐变的动画效果来实现的。目前Android应用框架支持的补间动画效果有以下5种。具体实现在android.view.animation类库中。
AlphaAnimation:透明度(alpha)渐变效果,对应<alpha/>标签。
TranslateAnimation:位移渐变,需要指定移动点的开始和结束坐标,对应<translate/>标签。
ScaleAnimation:缩放渐变,可以指定缩放的参考点,对应<scale/>标签。
RotateAnimation:旋转渐变,可以指定旋转的参考点,对应<rotate/>标签。
AnimationSet:组合渐变,支持组合多种渐变效果,对应<set/>标签。
补间动画的效果同样可以使用XML语言来定义,这些动画模板文件通常会被放在Android项目的res/anim/目录下。比如,代码清单2-36中就定义了一个组合式的渐变动画效果。
代码清单2-36
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="1000" />
<scale
android:fromXScale="0.1"
android:toXScale="1.0"
android:fromYScale="0.1"
android:toYScale="1.0"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="100" />
</set>
以上补间动画有两个效果:首先,在1秒(1000ms)的时间内,透明度从0(完全透明)变成1(不透明);同时,大小从原先的1/10变成正常大小,缩放的中心点是元素的中心位置。假设以上动画效果的模板文件名为demo_tween_anim.xml,现在我们要把该动画效果应用到一张ID为img_tween_anim的图片上,实现方法见代码清单2-37。
代码清单2-37
...
ImageView iv = (ImageView) findViewById(R.id.img_tween_anim);
Animation anim = AnimationUtils.loadAnimation(this, R.anim.demo_tween_anim);
iv.startAnimation(anim);
...
在实际项目中,我们经常使用补间动画,原因是补间动画使用起来比较方便,功能也比逐帧动画强大不少,而且还可以很方便地进行动画叠加,实现更加复杂的效果。实际上,代码清单2-36中的<set/>标签对应的就是AnimationSet类,即“动画集合”的概念,支持加入多种动画效果,如渐变动画(alpha)、大小动画(scale),线性动画(translate)等。另外,在Android系统中,所有与动画相关的类都归类在android.view.animation包之下,大家可以参考SDK文档进行进一步学习。
至此,我们已经初步了解了如何在Android系统中使用各种动画效果,包括逐帧动画和补间动画。显而易见的是,在Android平台之上,开发者们可以很方便地使用各种动画效果来为应用产品增色。此外,使用动画效果还可以帮助我们制作出简单的Android游戏,更多与Android游戏开发有关的内容请参考本书第13章。
2.10Android开发环境
前面我们已经学习了Android系统中最重要的基础概念的内容,那么接下来就要开始正式进入Android应用的实战开发阶段。“工欲善其事,必先利其器”,因此,我们先来熟悉Android应用的开发环境吧。
Android应用的开发环境是基于Eclipse平台的,Eclipse的强大无需多说,它当然也适应于Windows XP、Mac OS、Linux等多种操作系统。另外,我们还需要安装一些必备的开发工具包,所需要的软件见表2-8。
表2-8Android应用的开发环境必备的开发工具
软件版本下载地址
Android SDKAndroid SDK 2.2http://developer.android.com/sdk/index.html
Java SDKJDK 1.6http://java.sun.com
EclipseClassic版本http://www.eclipse.org
ADT最新版本https://dl-ssl.google.com/android/eclipse/
2.10.1开发环境的搭建
在搭建开发环境之前,我们先来介绍一下Android开发环境的几个重要组成部分以及它们的安装方式。
1. Android SDK
Android SDK的安装非常简单。首先,直接打开前面提到的Android SDK的下载地址,下载最新的android-sdk_r13-windows.zip安装包;下载完毕之后,在电脑上解压,你会发现android-sdk-windows这个目录,这个目录就是Android SDK目录了,你可以把它复制到你所希望的位置并重新命名,比如D:\Android;然后,打开SDK Manager.exe,你会看到如图2-16所示的安装界面。接下来,单击“OK”按钮让下载过程继续就可以了。系统会自动下载最新的Android SDK及其文档和例子等。当然,这个过程是很漫长的,如果有可能,建议从已经下载过的朋友那里复制一份。
图2-16 Android SDK安装界面
2. Java SDK
Java SDK的安装过程也是很简单的,不过下载地址可能有点难找,如果找不到请尝试从以下地址下载:http://www.oracle.com/technetwork/java/javase/downloads/index.html。下载完最新版的JDK版本之后,使用软件自动安装即可。要注意的是,在安装完毕之后需要设置Windows系统的环境变量,如图2-17所示。
图2-17Java SDK安装界面
设置完毕之后,我们可以在Windows命令行中使用“java -version”命令行来检测JDK是否安装成功。如果运行结果如图2-18所示,则表示安装成功。
图2-18检测JDK是否安装成功
3. Eclipse
Eclipse开发工具的安装也是非常简单的,进入http://www.eclipse.org/downloads/页面,下载Eclipse Classic最新版本的ZIP压缩包,解压缩后再复制到相应目录,比如D:\Eclipse。打开eclipse.exe就可以看到以下界面,如图2-19所示。
图2-19Eclipse界面
由于前面已经安装过Java SDK,所以直接打开eclipse.exe就会看到以上界面,否则打开时会提示错误。下面为没有使用过Eclipse的朋友大致介绍一下Eclipse的操作界面:最上面的那一排文字是“选项菜单栏”,包括几乎Eclipse中所有的操作;“选项菜单栏”的下面那排是常用项目的“快捷图标栏”;左边是Package Explorer,即“项目文件浏览框”,主要用于管理项目代码;中间是“代码编辑框”,我们在这里编辑代码;右边是Outline“代码大纲框”,这里可以方便地进行代码概览;右下方则是“调试信息框”,这里面包括Problems错误提示框、Console调试信息结果框等。
4. ADT
实际上,ADT(Android Development Tools)是Eclipse开发工具的一个插件,其安装过程也很简单:首先单击Eclipse界面上方的“Help”菜单,然后选择“Install New Software ”命令,接着在“Work with”输入框输入ADT插件地址“https://dl-ssl.google.com/android/eclipse”,单击“Add”按钮添加插件站点即可。当下方窗口出现选项列表时,单击选择所有的安装选项,然后按照提示安装即可,如图2-20所示。
安装完成后,会在左上方的“快捷图标栏”中出现ADT的快捷图标,即。单击此图标,系统会自动打开“Android SDK and AVD Manager”(Android虚拟设备管理器)界面,如图2-21所示。在这里我们可以创建并管理我们所需要的虚拟设备。此时,右边的“虚拟设配列表”中是空的。
在真正地开始创建设备之前,我们还需要配置一下ADT中Android的SDK位置,配置过程如下:执行“Window”菜单中的“Preferences”命令,然后选择左边的“Android”选项,然后在右边的“SDK Location”中选择Android SDK安装的位置。比如,之前我们把Android SDK安装到D:\Android目录下,那么我们在这里就选择该目录,如图2-22所示。
图2-20ADT插件安装界面
图2-21Android虚拟设备管理器
图2-22配置ADT
Android虚拟设备管理器是用来运行和调试我们所开发的Android应用程序的,它可以模拟各个版本几乎所有的Android设备。如果你要添加一个新设备,就单击右边的“New”按钮,并按照图2-23配置所需要的设备,最后单击“Create AVD”完成创建,结果会在“虚拟设配列表”中显示,如图2-24所示。
图2-23创建虚拟设备
这里简单介绍一下Android虚拟设备的主要配置选项。
Name:虚拟设备的名称。
Target:设备的Android API版本,考虑到兼容性,这里选择Android 2.2的API。
SD Card:设备的存盘大小。
Skin:设备外观,我们可以选择主流的设备,也可以直接指定设备的宽度和高度。
Hardware:设备硬件,如果你的设备需要有一些特殊的硬件,可以在这里进行配置。当然,还可以使用右侧的“New”按钮来添加所需要的虚拟硬件设备。
图2-24虚拟设备创建成功
图2-24所示的就是创建完毕的AVD的虚拟设备列表界面,大家可以看到这次在右侧的设备列表中已经多出一个名为“Android_2.2”的AVD虚拟设备,我们可以选中它,然后单击右边的“Start”按钮,然后等待一段时间,即可看到虚拟设备界面,效果还是很不错的,如图2-25所示。
图2-25成功运行虚拟设备
当然,如果你觉得虚拟设备的速度太慢,我们也可以使用真机来调试。其实,操作起来也很简单,安装步骤如下。
步骤1:安装手机的驱动,保证手机在Windows XP上可以被识别。
步骤2:打开手机的“设置”,然后选择“应用程序”中的开发选项,打开“USB调试”和“允许模拟地点”选项。
步骤3:打开Eclipse中的DDMS,在左边的Devices列表中就可以看到你的真机设备,单击选中它,就可以开始在真机上进行安装和调试了。
之后,我们就可以通过USB连接线把手机设备与开发机器连接起来,直接把Android应用程序安装到手机设备上进行调试。实际上,真机调试是正规Android应用程序发布的必要步骤,因为Android的手机设备型号非常多,所以在上线之前应尽量多测一些手机设备,保证Android应用的兼容性。
2.10.2首个Android项目
前面我们已经把Android的开发环境准备好了,下面我们将使用Eclipse+ADT来创建自己的首个Android项目,也就是我们常说的Hello World项目,具体步骤如下。
步骤1:打开Eclipse开发工具,单击左上方的“新建项目”菜单创建一个项目,然后选择“Android Project”子项,单击“Next”按钮,如图2-26所示。
步骤2:在接下来的新建项目界面中的“Project Name”(项目名)文本框中填入项目的名字hello;在“Build Target”选项组中选择“Android 2.2”,这里的选项应该和前面建立AVD时采用的Android版本保持一致;在“Package name”文本框中填写包名“com.app.hello”;在“Create Activity”文本框中填入需要建立的默认Activity类名HelloActivity,如图2-27所示。
小贴士:以下的“hello”项目同“Hello World”项目。
图2-26创建Android项目
图2-27项目创建界面
步骤3:单击“Finish”按钮,ADT会自动生成代码并把项目建好。完成之后,我们就可以在Eclipse界面左边的“Package Explorer”窗口中看到创建完毕的名为“hello”的项目了。接下来,我们试着发布并运行此项目。右键单击hello项目,在快捷菜单中执行“Run As”→“Android Application”命令,如图2-28所示。
图2-28运行hello项目
步骤4:Eclipse会帮助我们自动完成代码编译工作,并安装到Android模拟器上运行,hello项目的最终运行效果如图2-29所示。
图2-29hello项目运行效果
至此,我们已经成功建立了自己的首个Hello World项目,虽然没有写过一行代码,这就是使用ADT环境给我们带来的好处。接下来,我们就以Hello World项目为例,分析一下Android应用的几个主要组成部分。首先,我们来看一下应用的基础配置文件,也就是AndroidManifest.xml文件,见代码清单2-38。
代码清单2-38
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.hello"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".HelloActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
可以看到,项目应用配置文件AndroidManifest.xml中不仅声明了Hello World应用的package名、版本号android:version、最小的sdk版本限制android:minSdkVersion等,还在<application>元素里面声明了应用中唯一的Activity,也就是HelloActivity的配置信息。此外,在hello项目的配置文件中,我们还需要注意以下几点。
package名就是应用安装时用的类包名,务必保证该名不和其他应用重名,否则安装时会发生严重冲突。
<activity>元素中的android:name必须和相关的Activity类对应上,比如“.HelloActivity”应该和“com.app.hello.HelloActivity”类对应。
<intent-filter>元素用于决定activity的调用方式,比如“android.intent.action.MAIN”就说明这个Activity是该应用的总入口,一个应用有且只能有一个MAIN入口Activity。
由于Hello World项目比较简单,大家在这里只能看到项目应用配置文件很小一部分的用法,比如Android应用的基础声明、Activity组件的简单配置等。关于配置文件其他更高级的用法,比如除Activity之外的其他重要组件的配置方法,以及关于消息过滤器<intent-filter>的完整用法等,我们都将在实战篇中的7.1.1节中详细介绍。
接下来,打开源码目录src/下的com.app.hello代码包中的主要界面程序的Java程序HelloActivity.java的代码,如代码清单2-39所示。该类的代码逻辑比较简单,HelloActivity类继承了Activity基类;接着,在此类的onCreate接口中使用了setContentView方法设置本Activity所使用的layout模板;最后,在运行的时候,系统就会展示出对应的UI界面。
代码清单2-39
package com.app.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
读到这里,也许大家会有疑问,虽然我们设置了R.layout.main模板,但是模板文件在哪里呢?要解决这个问题,我们需要熟悉一下Android的基本目录结构,我们就以Hello World项目为例,项目的目录结构如图2-30所示。
对照图2-30,我们来讲解一下Android项目中常见的目录结构。
hello/src/com.app.hello:程序包目录,这里根据前面新建项目的时候填写的“Package Name”选项来生成应用程序包的Namespace命名空间。
hello/gen/com.app.hello:存储ADT自动生成的资源映射文件R.java,此文件内的静态类分别对应着Android不同的资源类型,而类中的静态类变量就表示对应资源的id标识,比如,R.layout.main对应hello/res/layout/main.xml布局文件。
hello/res/drawable-hdpi:图片以及渲染文件存储目录,在Android 2.1之后,原先的drawable目录被扩充为3个目录drawable-hdpi、drawable-ldpi和drawable-mdpi,主要是为了支持多分辨率,drawable-hdpi存放高分辨率图片,如WVGA(480×800),FWVGA(480×540)。
hello/res/drawable-ldpi:中等分辨率图片存储目录,如HVGA(320×480)。
hello/res/drawable-mdpi:低分辨率图片存储目录,如QVGA(240×320)。
hello/res/layout:布局文件存储目录,这里就是存储layout模板的地方了。
hello/res/values:配置文件存储目录,如strings.xml、colors.xml等。如果你要开发Android的国际化程序,可以在这里为不同的地区所支持的语言设置不同的目录,比如中文简体hello/res/values-zh-rCN,而中文繁体则为hello/res/values-zh-rTW。
hello/AndroidManifest.xml:每个Android项目都必需的基础配置文件,除了声明程序中的Activities、ContentProviders、Services和Intent Receivers,还可以指定permissions和instrumentations(安全控制和测试)等。
hello/proguard.cfg:主要用于Android应用代码的安全混淆。
理解以上内容之后,可以尝试着动手给Hello World项目的代码做一些小修改,比如,调整一下打印出来的文字,或者修改一下布局的方式等。这样不仅可以加深对Android项目开发的印象,还可以帮助大家快速地熟悉Android应用的开发工具,为后面的项目实践做准备。
2.10.3使用DDMS调试工具
在完成了首个Hello World项目的创建之后,大家应该可以体会到在Eclipse加上ADT的开发环境中进行Android代码开发是一件多么方便的事情。而实际上,ADT还给我们提供了一个非常方便的调试工具,那就是DDMS。使用这个工具,代码调试工作也变得简单起来。我们只需要单击Eclipse界面右上方的DDMS按钮就可以切换到DDMS界面了,如图2-31所示。
图2-31DDMS调试界面
接下来,我们按照“从左到右,从上到下”的顺序介绍一下该工具中的几个主要功能板块的功能和使用。
Devices:该窗口用于显示所有设备的详细信息,这里的emulator-5554就是模拟器设备的编号,下面则是设备运行的所有进程的列表,单击相应的进程还可以进行调试、截屏等动作。
Emulator Control:这里主要用于操控一些模拟器的行为,比如设置GPS定位信息等。
File Explorer:本窗口是Android系统的文件浏览器,在这里,我们可以浏览设备里面的文件目录,比如,之前在讲Android数据存储的时候提到过可以使用DDMS来浏览对应的存储文件,讲的就是这个窗口的功能。
LogCat:用于打印设备的调试信息,这个窗口应该是在开发过程中最经常用到的了,这里的信息分为五级,分别对应上面的V(VERBOSE)、D(DEBUG)、I(INFO)、W(WARN)、E(ERROR)五个圆形的按钮。此外,还可以通过单击这些按钮来过滤相应的调试信息。
Console:控制台打印的主要是操作信息,在这里,可以查看设备的运行情况,比如应用的apk包是否安装成功等。
在这些功能板块中,我们重点介绍一下LogCat窗口的使用,因为开发的时候最经常使用到的就是它了。在Android程序中,我们可以使用android.util.Log类里面的方法来打印不同级别的信息,笔者个人在调试的时候比较喜欢使用WARN级别,因为INFO以上的信息太多了,不利于过滤,而ERROR又太严重,经常和一些Exception混起来。另外,笔者个人还非常喜欢直接把它拉到开发界面中去,这样不需要切换到DDMS就可以调试程序了。以上是笔者本人的一些使用心得,如果你觉得不错的话不妨试一试。
当然,DDMS的用法不只有上面提到的这些功能,关于DDMS的使用心得,大家应该在Android应用的开发和调试中注意积累。另外,本书实战篇中的7.1.3节也会结合实际应用进一步说明DDMS工具的用法。总之,学会如何灵活地使用DDMS来调试Android应用程序是Android应用开发中必不可少的知识和技巧。
2.11小结
在本章中,首先我们学习了Android的系统框架和应用框架,然后熟悉了Android的四大核心要点和四大组件(活动Activity、服务Service、广播接收器Broadcast Receiver、内容提供者Content Provider),以及Android中一些常用的数据存储方式。随后,我们还学会了如何安装和配置Android的开发环境,并且动手开发了第一个Android应用Hello World项目,还学习了一些使用DDMS进行调试的方法。
最后,建议大家回顾一下本章的所有知识点,如果感觉都已经理解掌握了的话,那么要恭喜,你已经成功迈出成为Android大师的第一步;当然,如果你感觉思路还有点不够清晰的话,请回头好好回顾并理解一下本章的内容,因为这些知识对你以后继续深入地学习Android系统是非常重要的。
第3章PHP开发准备
通过本章,读者将快速地学会如何使用PHP语言进行服务端开发。当然,如果你之前已经有过一些服务端开发的基础,学习本章内容将会更加轻松;然而,如果你以前一直专注于客户端开发,则更需要仔细阅读本章的内容,因为本章将通过讲解PHP语言引领你进入服务端开发的世界,并理解一些服务端开发通用的方法和思路。
本章首先会给大家介绍PHP的开发基础,以及一些面向对象编程的技巧;然后紧接着给大家介绍PHP开发环境(Xampp)的搭建和一些其他主要的与之配套的服务端组件(Apache和MySQL)的基础管理;最后还会介绍一个强大的基于Zend Framework和Smarty类库的PHP框架:Hush Framework,本书核心的“微博实例”正是采用这个PHP框架,并使用了其中的MVC分层开发的思路进行开发的。
3.1PHP开发基础
编写本章之前,笔者在考虑一个问题,那就是“如何把一本书的内容压缩到短短的一章中”。这确实是一个难题!但是,虽然篇幅有限,本书还是会尽量使用最简洁明了的语言和最易于理解的实例,来帮助大家以最快的速度认识和了解PHP的开发。
3.1.1PHP语言简介
PHP(Hypertext Preprocessor)是目前最流行的服务端脚本语言之一。近年来,随着互联网的飞速发展,使用PHP语言进行互联网应用开发也变得逐渐火热起来,其特点是简单、快速、灵活,主要应用于各大门户网站、主流CMS平台以及Web 2.0网络应用中,包括Google、Yahoo、Facebook、Zynga在内的互联网巨头们也都大规模地使用PHP作为其主要的编程语言。
那么,PHP究竟能用来做什么呢?一般来说,PHP在实际项目的应用过程中有以下两种主要的使用方式。
1. 用于后台脚本编程,即以命令行(CLI)的方式执行
由于PHP的语法和Linux Shell语言有点类似,而使用起来却要比Shell强大且方便得多,所以我们经常使用PHP作为后台可执行脚本的解决方案。这种方式下的PHP脚本,我们也常称之为CLI(Command-Line Interface)脚本。
2. 用于网络应用编程,即以mod_php或fastCGI的方式执行
简单说就是用于开发网站或者互联网应用,这也是PHP最主要的使用方式。在这种方式中,PHP经常和其他的一些服务端组件结合使用,比如在著名的LAMP架构里,PHP就是与Apache服务器、MySQL数据库组成了互联网应用服务端开发的铁三角。PHP的这种使用方式通常被称为网络(Web)脚本模式。
小贴士:LAMP即Linux、Apache、MySQL以及PHP/Perl/Python相结合的服务端解决方案,也是目前最强大的互联网应用解决方案之一,占据了全球的网站70%以上的市场。简单易用、性能强劲、完全免费这三个特点是LAMP为何如此受欢迎的原因之一。
3.1.2PHP语法简介
了解过PHP语言的用途,接下来我们来看看如何使用PHP。首先,来学习一下PHP基本语法中的重点部分,以下就是“精简版”的PHP语法总结。当然,如果读者已经有过一些其他主流语言的编程经验,比如Java、C++等,那么笔者建议学习时,可以将PHP的语法与这些已经比较熟悉的语言进行对比学习,这样会事半功倍。
1. 规范
PHP代码部分需要用“<?php … ?>”符号框起来,这也表明你可以把PHP代码块嵌入到HTML代码的任何位置,这种用法类似ASP或者JSP。
2. 注释
PHP中单行注释以“//”或者“#”符号开始,多行注释使用“/* … */”符号框起来,这点综合了Perl、C++以及Java语言的用法。
3. 变量
PHP的所有变量都以“$”符号开始,变量的命名规则与C++和Java语言的标准基本相同,例如:$_user是正确的,$@user就是错误的。另外,由于PHP是解释性语言,具有弱类型性,所以PHP的变量不需要声明类型,这点与Java和 C++这些编译型的强类型语言是不同的。
4.常量
PHP使用define函数来定义常量,这点类似于C和C++语言。常量名我们一般都会使用全大写的字母,比如“define('CONSTANT', $constant);”这行代码就定义了一个值为$constant的CONSTANT常量。
5. 函数
自定义PHP的函数必须包含function关键字,比如“function hello () {...}”。此外,PHP语言的自带函数库是非常强大的,这点大家可以在日后使用中慢慢体会。
6. 类定义
定义PHP类的方法和Java基本一致,比如“public class User {...}”。另外,在PHP 5发布后,PHP的面向对象功能越加强大,具体可参考本章3.1.4节的内容。
7. 允许文件中包含文件
在PHP中允许包含其他的PHP文件,这样方便了我们进行代码的封装,一般来说使用require和include方法来包含。如果要避免重复包含的问题,则可以使用require_once和include_once方法。
8. 命名空间
对于大型的项目来说,命名空间(Namespace)的功能还是非常必要,使用命名空间可以减少因为类名或者函数名相同所带来的风险。在PHP的新版本中(PHP 5.3),已经支持namespace语法,比如“namespace Core\Lib1”。
事实上,PHP的语法源自Perl语言,并融合了Java和C语言的部分优点,对于有一定编程基础的开发者来说上手非常快。首先,我们来观察一个PHP的Hello World程序,如代码清单3-1所示。
代码清单3-1
<?php
// 打印字符串
echo "Hello World";
?>
从这段代码中我们可以看到一个标准PHP脚本的写法、打印字符串的方法echo,以及单行注释的写法。
小贴士:在实际开发时,我们经常把PHP文件最后的“?>”符号去掉,因为这样写不仅不会影响PHP的语法解释,还可以避免一些由于编辑器在文件的末尾处自动加上特殊字符,从而影响PHP解释和输出的问题。
接下来,我们来分析代码清单3-2中的PHP的程序范例。代码逻辑非常简单,最前面定义了一个名为“USERNAME”的常量,接着定义了一个函数isJames()用于判断输入的参数是否等于“James”,最后打印函数的测试结果。很显然这段代码的运行结果是false,因为传入值和比较值的大小写是不一样的。
代码清单3-2
<?php
// 常量定义
define('USERNAME', "James");
// 函数定义
function isJames ($username) {
if (USERNAME == $username) {
return true;
}
return false;
}
// 打印结果
var_dump(isJames("james"));
?>
以上代码包含了PHP语言中的注释、变量、常量以及函数等重要语法的使用方法,大家可以尝试在本地运行该脚本。运行方法很简单,直接使用php可执行文件执行即可,比如该php文件名为demo1.php,用户直接在系统命令行窗口中输入“php demo1.php”并运行即可。当然,在此之前我们还必须把php可执行文件的路径加入到系统环境变量中去,否则系统可能提示找不到php命令。代码清单3-2的运行结果如图3-1所示。
图3-1代码清单3-2的运行结果
3.1.3PHP开发起步
通过3.1.2节的学习,我们大致了解了PHP的基本语法,本节我们将进一步认识PHP语言。首先,我们来看看PHP语言的几个特色,也就是它和其他语言不大一样的地方。
1. 预定义变量
PHP提供大量的预定义变量,准确来说应该是预定义“数组”变量,用于存储来自服务器、运行环境和输入数据等动态信息,不同于其他语言使用对应的使用类包或者方法来获取的方式,相对来说PHP的这种方式更加简单直接。表3-1中列出了PHP语言中比较重要的预定义变量。
表3-1PHP的重要预定义变量
变量名环境作用
$GLOBALS—引用全局作用域中可用的全部变量
$_SERVER—服务器和执行环境信息
$_GETWebHTTP GET 变量
$_POSTWebHTTP POST 变量
$_FILESWebHTTP 文件上传变量
$_COOKIEWebHTTP Cookies
$_SESSIONWebSession 变量
$_REQUESTWebHTTP Request 变量(包含HTTP GET/POST)
$_ENV—系统环境变量
$http_response_headerWebHTTP 响应头
$argcCLI传递给脚本的参数数目
$argvCLI传递给脚本的参数数组
以上这些预定义变量都是我们在开发过程中经常使用到的,但是需要注意的是它们中某些变量的使用环境是有限制的,比如$_GET、$_POST以及$_SERVER变量在CLI模式下是没有作用的,因为CLI不运行在服务器环境里。关于这点我们已经在上表的“环境”一列中说明了,其中标注Web的表示只有在网络脚本模式下才能使用;CLI表示只工作在CLI脚本模式下;其他的预定义变量在两种模式下均可使用,但是数组的内容可能会不同。
以下是一个使用预定义变量的例子,不像Java还需要使用request.getParameter()方法逐个获取GET参数,PHP会直接把所有的GET参数全部放到预定变量$_GET中,我们可以直接循环打印出来,如代码清单3-3所示。
代码清单3-3
<?php
$sp = "
\n";
foreach ((array) $_GET as $k => $v) {
echo "GET $k : $v".$sp;
}
?>
由于这个脚本必须在Web模式下才能使用,因此我们需要把以上代码放入站点目录下,开启浏览器,运行结果如图3-2所示。
图3-2代码清单3-3的运行结果
2. null、false、0和空字符串之间的关系
在PHP中,如果一个变量没有赋值则为null,这点和Java类似;但是,PHP是一门“弱类型”的语言,因此对于PHP来说,null和false、0以及空字符串之间的关系有点特殊,如代码清单3-4所示。
代码清单3-4
<?php
// null and 0
echo "null==0 : ";
echo var_dump(null==0);
// null and ''
echo "null=='' : ";
echo var_dump(null=='');
// null and false
echo "null==false : ";
echo var_dump(null==false);
// null and 0
echo "null===0 : ";
echo var_dump(null===0);
// null and ''
echo "null==='' : ";
echo var_dump(null==='');
// null and false
echo "null===false : ";
echo var_dump(null===false);
?>
以上脚本的运行结果如图3-3所示。我们来分析一下结果:首先,前3段代码说明了在PHP中null、false、0和空字符串之间是可以画等号的,这是因为它们在PHP都属于一种“非”类型,其他的“非”类型还包括空数组等;其次,看后3段代码,我们又可以发现,如果我们要区别这几个值也是可以做到的,在PHP中我们使用全等号便可以判别出它们之间的不同。我们在日常编码时一定要注意PHP的这个特性,如果随意乱用,很有可能会犯一些低级错误。
图3-3 代码清单3-4的运行结果
表3-2列出了PHP语言中对于“非”类型的汇总信息,大家在使用PHP的过程中一定要特别注意这些数值的使用方法。
表3-2 PHP的“非”类型汇总
数值作用
null当变量未被赋值,则为null
0整型数值
false布尔类型
空字符串字符串"和""
空数组空数组Array()
3. 魔术变量和魔术方法
最初PHP魔术变量的出现主要是为了方便开发者调试PHP的代码;当然,我们也可以利用这些魔术变量来实现特殊需求。在写法方面,魔术变量前后都有两个下划线,接下来让我们来熟悉一下这些变量。
__LINE__:返回文件中的当前行号,我们在定位错误的时候经常用到。
__FILE__:返回当前文件的完整路径和文件名。自PHP 4.0.2起,__FILE__总是包含一个绝对路径(如果是符号连接,则是解析后的绝对路径)。
__DIR__:返回当前文件所在的目录(PHP 5.3.0中新增)。如果用在被包括文件中,则返回被包括的文件所在的目录,等价于dirname(__FILE__)。除非是根目录,否则目录中名称不包括末尾的斜杠。
__FUNCTION__:返回当前函数的名称(PHP 4.3.0中新增)。自PHP 5起本常量返回该函数被定义时的名字,大小写敏感。在PHP 4中该值总是小写字母的。
__CLASS__:返回当前类的名称(PHP 4.3.0中新增)。自PHP 5起本常量返回该类被定义时的名字,大小写敏感。在PHP 4中该值总是小写字母的。
__METHOD__:返回当前类的方法名(PHP 5.0.0中新增)。注意与__FUNCTION__的返回有所不同,大小写敏感。
__NAMESPACE__:返回当前命名空间名(PHP 5.3.0中新增)。这个常量是在编译时定义的,大小写敏感。
魔术方法主要是随着PHP的面向对象特性出现的(也就是在PHP 5之后),主要解决的是PHP在面向对象的思想中所遇到的一些特殊情况,写法方面和魔术变量类似,魔术方法使用两个下划线开头,接下来学习常用的魔术方法。
__construct():通用的类构造函数。
__destruct():通用的类析构函数。
__get(string $name):当试图读取一个并不存在的类属性时被调用。
__set(string $name, mixed $value):给未定义的类变量赋值时被调用。
__call(string $name, array $arguments):当调用一个不可访问类方法(如未定义或不可见)时,__call() 会被调用。
__callStatic(string $name, array $arguments):当调用一个不可访问的静态类方法时,__callStatic()方法会被调用。
__toString():当打印一个类对象时被调用,这个方法类似于Java的toString方法。
__clone():当类对象被克隆时调用。
__sleep():持久化一个类对象时,如果__sleep()方法存在则先被调用,然后才执行序列化操作。这个功能可以用于清理对象,比如你有一些很大的对象,不需要持久化,这个功能就很好用。
__wakeup():与__sleep()相反,在反持久化类对象时,如果存在__wakeup()方法,则使用该方法预先准备对象数据。__wakeup()可用在类似于重新建立数据库连接等初始化操作中。
__isset():当对未定义的类变量调用isset()或empty()时,__isset()会被调用。
__unset():unset一个对象的属性时被调用。如:unset($class->name)。
__invoke():当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用。
__autoload():区别于以上所有方法,__autoload()并非是一个类方法,而是一个全局方法。在实例化一个对象时,如果对应的类不存在,则该方法被调用,可用于类的自动加载。
另外,别忘了所有的魔术方法都需要给予public属性。关于魔术变量和魔术方法的应用如代码清单3-5所示。
代码清单3-5
<?php
class ClassA {
// 私有变量
private $secret;
// 给私有变量赋值
private function setSecret () {
$this->secret = "my secrets";
}
// 构造函数
public function __construct () {
echo "CALL ".__METHOD__."\n";
$this->setSecret();
}
// 析构函数
public function __destruct () {
echo "CALL ".__METHOD__."\n";
}
// 魔术方法 __get
public function __get ($name) {
echo "CALL __get:".$name."\n";
}
// 魔术方法 __set
public function __set ($name, $value) {
echo "CALL __set:".$name.",".$value."\n";
}
// 魔术方法 __call
public function __call ($name, $arguments) {
echo "CALL __call:".$name.",".print_r($arguments, true)."\n";
}
// 魔术方法 __sleep
public function __sleep () {
echo "CALL ".__METHOD__."\n";
$this->secret = "unknown";
return array("secret");
}
// 魔术方法 __wakeup
public function __wakeup () {
echo "CALL ".__METHOD__."\n";
$this->setSecret();
}
}
$a = new ClassA();// 初始化 ClassA
$a->attrA = "valueA";// 赋值不存在的属性 attrA
echo $a->attrB;// 获取不存在的属性 attrB
$a->hello(1,2,3);// 调用不存在的方法 hello()
$b = serialize($a);// 持久化 ClassA
var_dump($b);// 打印持久化后的对象
$c = unserialize($b);// 反持久化 ClassA
var_dump($c);// 打印反持久化后的对象
?>
以上程序先定义了一个类ClassA,此类定义了一个$secret属性,还定义了我们上面介绍到的一些主要的魔术方法,接下来执行了以下步骤,我们来逐一分析。
1)初始化 ClassA:调用构造函数__construct(),并对$secret变量赋值。
2)赋值不存在的属性 attrA:调用魔术方法__set()。
3)获取不存在的属性 attrB:调用魔术方法__get()。
4)调用不存在的方法 hello():调用魔术方法__call()。
5)持久化 ClassA:调用魔术方法__sleep(),并隐藏$secret变量的值。
6)反持久化 ClassA:调用魔术方法__wakeup(),并恢复$secret变量的值。
7)最后回收对象:调用两次析构函数__destruct(),原因是这里产生了两个对象实例,$a和$c。
该程序的最终运行结果如图3-4所示,大家可以结合上面的分析来思考。
图3-4代码清单3-5的运行结果
小贴士:代码清单3-5中的print_r方法的功能主要用于打印PHP数组。
4. 神奇的PHP数组
记得多年以前有位做Java开发的同事曾经问过我这样一个问题:“为什么PHP的数组这么好用呢?”当时我觉得不以为然,不过现在回过头来想想,这个问题确实值得好好讨论一下。要解决这个问题,首先我们要理解一点:对于PHP来说,没有集合(Set)、栈(Stack)、列表(List)以及散列(Hash)的概念,所有这些常见的数据结构都在PHP的数组里面实现了。我们先来看一段关于PHP数组操作的代码,如代码清单3-6所示。
代码清单3-6
<?php
$arr = array(1,2,3);
// 集合用法
echo '$arr[0]:'.$arr[0]."\n";
// 栈用法
array_push($arr, 4);
echo '$arr:'.print_r($arr, true);
array_pop($arr);
echo '$arr:'.print_r($arr, true);
// 列表用法
array_push($arr, 4);
echo '$arr:'.print_r($arr, true);
array_shift($arr);
echo '$arr:'.print_r($arr, true);
// 散列用法
$arr[3] = 5;
echo '$arr[3]:'.$arr[3]."\n";
?>
从代码中的注释可以看出,以上程序分别模拟了集合、栈(后进先出)、列表(先进先出)以及散列数组的用法,大家可以体会一下,该程序的运行结果如图3-5所示。
图3-5代码清单3-6的运行结果
之前我们讨论的都是最为简单的一维数组,而在实际项目中我们经常使用的是其他组合形式的数组,比如,和数据库表结构所对应的“散列数组列表”的形式,下面我们再来看一个例子,见代码清单3-7。
代码清单3-7
<?php
// 散列数组列表
$arr = array(
array(
"name" => "James",
"sex" => "M",
"age" => "28"
),
array(
"name" => "Iris",
"sex" => "F",
"age" => "27"
)
);
?>
<table border=1 cellspacing=1 cellpadding=5>
<tr><td>Name</td><td>Sex</td><td>Age</td></tr>
<?php foreach ($arr as $row) { ?>
<tr><td><?=$row["name"]?></td><td><?=$row["sex"]?></td><td><?=$row["age"]?></td></tr>
<?php } ?>
</table>
在上述例子中,我们演示了一个“散列数组列表”的使用方法,实际上这种数据结构经常出现在我们从数据库中取出数据然后展现到页面表格中去的情况。除此之外,从本例中我们还可以学到如何在PHP中嵌入HTML标签,当然这已经是一种最古老的使用方法了,在实际项目中我们经常使用一些其他的模板引擎来负责展示部分,比如Smarty模板引擎(我们将在3.5节中介绍)。最后我们来看一下本例在浏览器中的运行效果,如图3-6所示。
通过以上的介绍和实例学习,相信大家对PHP的数组已经有了一定的了解,这种融合了列表、散列、栈等多种常用数据结构的“超级工具”可以说是PHP的一大发明;当然,PHP数组的用法绝不仅仅只有以上这些,通过日后继续深入学习,我相信你会越来越喜欢这个“编程利器”的。
3.1.4PHP面向对象编程
虽然之前我们已经学习了PHP语言的开发基础,但是应用这些知识来开发项目还远远不够,因为我们在实际的项目中都需要使用“面向对象”的写法来进行编程,如果只懂得基本语法却没有面向对象的编程思想是万万不行的。所以接下来我们将为大家介绍在PHP语言中,我们是如何使用面向对象的编程思路来进行开发的。
其实PHP语言的面向对象编程思路和Java非常相似,对象(Object)、类(Class)、抽象类(Abstract Class)、接口(Interface)以及MVC三层封装的用法都大同小异,由于篇幅限制,相同的地方将不再赘述,这里主要给大家介绍一下不同的地方,下面我们将结合面向对象编程思想中的几个重要概念来分析一下。
1. 抽象性
要认识面向对象的思想,首先需要理解什么是对象。对象是面向对象编程的基本元素,对象可以是我们需要研究的任何元素,可以是具体的物品,也可以是抽象的事物。比如我们在研发一个后台管理系统时,管理员是一个对象,后台权限也是一个对象。这就是抽象的基本思路,就这点来说,不管我们使用的是什么语言,思考方式应该是一样的。
此外,对象在程序代码中是用“类”(Class)来表示的,每个类都需要具备“唯一性”的特征,在大部分语言中都使用“命名空间”来解决这个问题;然而对于PHP来说,我们通常使用一种“约定”或者“规范”来解决这个问题,比如在PHP的一些大型类库(如Zend Framework)中我们通常把类名和目录名对应起来,Zend/Db/Exception.php文件的类名是Zend_Db_Exception,这种方式在项目中还是比较实用的。
2. 继承性
继承性是面向对象思想中最基础、最重要的特点之一,也正是因为对象的继承性才延伸出了“抽象”和“封装”等面向对象的设计方法。在PHP语言中我们同样使用私有(private)、保护(protected)和公共(public)关键字来设定类、属性和方法的权限,关于这些基础概念的基本用法大家可以到网上收罗到一大堆,这里不再赘述。下面我们将以一个PHP代码为例给大家讲解一下使用要领,见代码清单3-8。
代码清单3-8
// 基础抽象类
abstract class Base_Customer {
// 私有属性
private $_name = '';
// 私有方法
private function _checkName ($name) {
return is_string($name) ? $name : false;
}
// 保护方法
protected function _formatName ($name) {
return (string) $name;
}
// 公用方法
public function setName ($name) {
$this->_name = $this->_formatName($name);
}
// 公用方法
public function getName () {
return $this->_checkName($this->_name);
}
// 抽象方法,子类需要实现
abstract public function setPassword();
// 抽象方法,子类需要实现
abstract public function getPassword();
}
上述实例代码中我们定义了一个名为Base_Customer的用户抽象基类,里面有一个名为“$_name”的私有属性,表示用户的名字;为了让外部能够访问这个属性,我们添加了读取名字getName和设置名字setName两个public方法,供外部程序调用。另外,此类还有一个private方法_checkName用于确认用户名字是否有误,一个protected方法_formatName用于保证名字正确性。在代码的最后我们还定义了两个抽象方法,设置密码setPassword和取得密码getPassword两个方法,用于对用户的密码进行操作,这两个方法都需要在子类中实现的。从以上代码中我们可以看到PHP语言里大部分面向对象编程的写法,大家可以好好理解一下。
3. 多态性
多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果,这个特性进一步增加了面向对象编程思想的灵活性和重用性。我们知道,重载是实现多态性最常见的方法,比如下面就是使用Java语言来使用重载的实例代码,见代码清单3-9。
代码清单3-9
class Demo{
// 成员变量
int a, b, c;
// 构造函数
public Demo() {}
// 重载构造函数
public Demo(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
// 成员方法
int sum() {
return a + b + c;
}
// 重载成员方法
int sum(int d) {
return a + b + c + d;
}
}
我们看到上面用Java语言实现的Demo类中,构造方法Demo被定义了两次,根据传入参数的不同,方法逻辑也有所不同;另外,类中的成员方法sum也被定义了两次,同样可以根据不同的参数来处理不同的逻辑。但是这是Java的做法,在PHP中我们也可以这样做吗?答案是否定的,因为PHP语言不允许出现相同名称的方法,即使在同一个类中。那么我们应该怎么做呢?看看代码清单3-10中是怎么写的吧。
代码清单3-10
class Demo {
// 成员变量
public $a, $b, $c;
// 构造方法
public function Demo ($a = 0, $b = 0, $c = 0) {
if ($a) $this->a = $a;
if ($b) $this->b = $b;
if ($c) $this->c = $c;
}
// 成员方法
public function sum ($d = 0) {
if ($d) {
return $this->a + $this->b + $this->c + $d;
} else {
return $this->a + $this->b + $this->c;
}
}
}
我们可以看到,以上的PHP代码同样实现了一个Demo类,此类含有和前面Java版的Demo类同样的成员变量,却只有一个构造方法Demo和成员方法sum;但是有趣的是,通过对这两个方法的逻辑分析,我们会发现这里同样根据参数的不同实现了不同的逻辑。这是为什么呢?答案其实很简单,就是因为PHP允许设置参数的默认值。这种PHP特有的功能帮助我们用另外一种方式实现了多态性。
以上我们介绍的PHP面向对象编程的基础知识,需要大家好好理解一下,因为在后面我们即将给大家介绍的微博应用实例中,这些用法将会被广泛使用;另外,在本章最后介绍的Hush Framework框架中我们也会接触到这些用法。当然,培养成熟的面向对象的编程思想绝不是一朝一夕的事情,需要大家在学习的时候边实践边思考,最好能阅读一些比较优秀的代码,当然本书后面将要给大家介绍的微博项目实例代码也是个很不错的面向对象编程的代码范本,如果大家能通过本书将其理解透彻,绝对会受益匪浅。
3.1.5PHP的会话
业内常说“不理解会话(Session)的概念就等于不懂得PHP网络编程”。当然,这里讲的是PHP语言用于互联网编程的时候。因为HTTP协议是无状态的,所以每次请求结束后,变量和资源就被回收了,那么我们如何保存一些“持久”的信息呢?比如在用户登录之后,系统需要把用户登录的信息保存下来,在整个应用或者站点里面使用。也许你会想到使用数据库来保存,当然这确实是一种解决方案,但是这些数据大部分属于临时数据,用户退出登录之后就没有用了,如果使用数据库不仅浪费资源,而且还需要定期清理,相当麻烦。为了解决这个问题,PHP专门为我们提供了会话模块,来保存这些临时的用户数据。
和大部分的语言环境一样,PHP的Session机制不是非常复杂,客户端只需要保存一个会话ID,即Session ID,每次会话请求都会把这个Session ID传给服务端,并获取服务端接口处理完的数据,整个过程如图3-7所示。
图3-7PHP的Session机制
PHP默认的会话存储方式是文件存储,数据会被保存到服务器本地的session.save_path参数设定的目录中(此参数位于php.ini配置文件)。使用的时候,首先需要调用session_start方法开启一个新的Session,然后直接使用PHP预定义变量$_SESSION来进行读取和存储操作,在请求结束时系统会把修改过的会话值保存到存储器中。示例用法如代码清单3-11所示。
小贴士:php.ini是PHP的环境配置文件,在Linux系统下一般会被放在/etc/php.ini目录下。该文件几乎包含了PHP运行环境所需的所有配置,也是我们必须学习的内容之一,由于篇幅原因,本书不做详细讲解。具体配置参数可直接参考官方文档,地址如下http://cn.php.net/manual/zh/ini.list.php。
代码清单3-11
<?php
session_start();
// 初始化计数器变量 $count
$count = isset($_SESSION['count']) ? $_SESSION['count'] : 0;
// 计数器依次递增
$_SESSION['count'] = ++$count;
// 打印计数器值
echo $count;
前面的会话实例实现了一个简单的计数器,逻辑很简单,大家参照着注释就可以很快读懂。这里需要注意的是,Session机制仅适用于有服务器的网络环境中,在以命令行(CLI)脚本运行的情况下是不起作用的。另外,我们可以看到该计数器程序的有效代码只有4行,这也从一个侧面反映了PHP语言的简单高效。
当然我们这里讨论的仅仅是比较简单的Session使用场景,对于相对比较大型一点的网络应用来说,Session的使用就不是这么简单了。比如我们要在多台应用服务器之间共享Session,那就不能把Session信息存放在本地了,这时候我们可能需要把Session集中存储在某个公用的中间件里,比如数据库或者缓存服务器等。好在PHP给我们提供了Session回调接口来帮助我们控制Session的存储方式,实例代码请参考代码清单3-12。
代码清单3-12
<?php
class SessionHandler {
protected $savePath;
protected $sessionName;
public function __construct() {
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
}
public function open($savePath, $sessionName) {
$this->savePath = $savePath;
$this->sessionName = $sessionName;
return true;
}
public function close() {
// 关闭 Session 逻辑
return true;
}
public function read($id) {
// 读取 Session 逻辑
}
public function write($id, $data) {
// 存储 Session 逻辑
}
public function destroy($id) {
// 销毁 Session 逻辑
}
public function gc($maxlifetime) {
// 回收 Session 逻辑
}
}
// 使用 Session 类
new SessionHandler();
上述实例中,我们使用session_set_save_handler方法重写了PHP的Session机制,通过这种方式我们可以很方便地控制Session的存取逻辑来满足我们的需求;此外,这也是我们优化Session机制时必须使用的知识,这些进阶知识我们会在本书的9.1.2节中给大家做进一步的介绍。
3.2PHP开发环境
前面我们已经学习了PHP编程语言的基础知识,接下来我们来了解PHP的开发环境。在此之前,我们先讨论一下PHP的开发工具。PHP是一种脚本语言,因此就语言本身特点而言,对开发工具没有什么严格的限制,从简单的Notepad和EditPlus到复杂的Zend Studio和Eclipse都可以进行PHP开发;但是在实际项目中,为了保证编码的一致性,以及代码版本管理的方便性,我建议大家在项目开发时使用Eclipse作为PHP编程开发的统一工具,如此一来,还可以和Android应用开发使用同一个开发工具,何乐而不为呢?
3.2.1开发环境的搭建
让Eclipse支持PHP有两种方式,其一是在本机的Eclipse开发工具上安装一款名为PHPEclipse的插件;不过现在我们一般使用另一种方式,也就是直接下载Eclipse专门为PHP开发者定制的开发工具PDT,下载地址为:http://www.eclipse.org/pdt/downloads/。
PDT的安装方法很简单,解压即可使用。但要注意的是,如果你的开发机之前没有安装过Java运行环境,PDT还是不能运行的,毕竟它还是要依靠Eclipse环境(环境搭建过程可参考2.10.1节)。当然,如果你想要把PHP开发环境和Android开发环境合为一体也是可以的,这就需要我们先下载PDT解压安装之后再安装ADT。
3.2.2安装配置Xampp
和Android客户端开发不同,进行PHP服务端开发,除了要安装语言本身的环境之外,还需要安装和配置服务端需要的组件,这也是服务端开发和客户端开发的不同之处。当然,PHP的集成开发环境有很多,本书为大家推荐一个方便实用的集成开发环境套件:Xampp。该套件是完全免费的,它集成了Apache服务器、MySQL数据库、PHP语言以及PERL语言等我们常用的服务端开发工具。
Xampp的下载地址非常多,利用搜索引擎可找到很多关于“Xampp下载”的链接,大家选择一个比较官方的链接点击下载即可。当然在本节中我们只会重点介绍这个工具的使用方法,如果你想了解更全面的关于Xampp开发环境套件的详细内容,可以上官方网站了解,网址为:http://www.apachefriends.org/zh_cn/xampp.html。
本书的开发环境是Windows,所以在下载完Xampp的Windows版本之后,我们需要将其安装到一个便于访问的目录下,比如D:\xampp目录,其中包含的文件如图3-8所示。
图3-8Xampp目录下包含的文件
从图3-8中我们可以看到Xampp还提供了很多额外的配套工具,我们先不看这些工具,找到“xampp-control.exe”文件,双击打开,会看到如图3-9所示的Xampp控制台界面。
在Xampp的控制台界面中,我们可以看到前两排分别是Apache和MySQL的控制按钮,我们单击这两个“Start”按钮就可启动Apache和MySQL了,非常方便。接着我们打开浏览器,输入“http://localhost”地址就可以看到Xampp的管理界面了,如图3-10所示。
Xampp管理界面可以支持多种语言,若要使用中文可以从页面右上方的语言选项中选择。另外,界面的左边是Xampp所有的功能选项,接下来,介绍其中比较重要的几个管理工具。
状态:Xampp主要组件的运行状态。
图3-9Xampp控制台
图3-10Xampp管理界面
安全:如果你想用Xampp作为正式环境,这个部分就很重要,因为这里涉及一些关于Xampp安全的注意事项。
文档:Xampp常用组件的文档,包括Apache、MySQL等。
phpinfo():此选项查看的是PHP的系统参数,比如,如果我们需要查找一些PHP的模块是否已经安装就可以在这里查看。
phpMyAdmin:MySQL数据库管理工具,关于此工具将在本章的3.2.4小节中做详细介绍。
Webalizer:简单小巧的Web日志分析工具,可做简单的访问分析。
Mercury Mail:Mail服务器,建议仅供调试。
FileZilla FTP:FTP服务器,建议仅供调试。
Xampp的管理工具看起来非常多,然而,在开发过程中经常用到的管理工具并不多,最经常用到的无非就是使用“phpinfo()”查看PHP环境参数,以及使用“phpMyAdmin”管理MySQL数据库等。
3.2.3管理Apache
Apache服务器是当今功能最为强大的HTTP服务器之一,也是目前全球市场占有率最高的HTTP服务器。因此,对于服务端开发者来说,如何管理Apache应该算是一个必须学习的内容;当然,如果你想仅仅通过本节就完全掌握Apache这是绝对不可能的,因为仅仅是Apache的日常管理文档就可以写成一本很厚的参考书。本节我们主要介绍一下在PHP服务端开发过程中,Apache服务器的基本用法。
由于Xampp环境已经帮助我们把Apache和PHP结合起来了,所以不需要做任何配置就可以让Apache支持PHP脚本。以下是一些在日常开发过程中常出现的操作,让我们来分别学习一下。
1. 启动和停止
在Xampp中启动和停止Apache非常简单,可直接在Xampp控制台中进行操控。如果有疑问可以参考3.2.2节的内容。
2. 设置虚拟主机(Virtual Host)
当我们开发一个新的网络应用时,首先,我们需要给这个网络应用分配一个域名,那么我们怎么在开发机上访问这个域名呢,我们假设现在要做的网络应用的域名是“http://test-app”,我们可以通过以下步骤来设置Apache的虚拟主机。
首先,我们需要找到并打开Windows本地的host文件,该文件位置如下:C:\WINDOWS\system32\drivers\etc\hosts,并在文件尾部加上如配置清单3-1所示的内容。
配置清单3-1
…
127.0.0.1 test-app
…
然后,我们再打开Apache的虚拟主机配置文件,该文件位于Xampp中的Apache目录下,如D:\xampp\apache\conf\extra\httpd-vhosts.conf,其中我们加入如配置清单3-2所示的配置信息。
配置清单3-2
…
NameVirtualHost *:80
<VirtualHost *:80>
DocumentRoot "/path/to/test-app"
ServerName test-app
<Directory />
AllowOverride All
Allow from all
</Directory>
</VirtualHost>
…
接着,我们在站点根目录(DocumentRoot)中放入一个测试PHP脚本,用于测试环境是否配置成功,如代码清单3-13所示。
代码清单3-13
<?php
phpinfo();
?>
最后,重启Apache服务器,打开浏览器并输入刚才准备好的PHP脚本文件进行测试,效果如图3-11所示。
图3-11info.php运行结果
如果看到的页面和上图的一样,那么表示我们的PHP网络脚本开发运行环境准备就绪了。学会这些之后,你还可以在该应用目录下编写其他的PHP脚本进行学习。
3.2.4管理MySQL
MySQL数据库绝对是现在市面上最为流行的开源数据库之一。实际上,PHP和MySQL在很早以前就被认为是互联网领域的“天作之合”,PHP为MySQL提供了非常稳定而高效率的数据库接口,而MySQL又为PHP提供了灵活而强大的数据存储方式,所以在学习PHP的同时,MySQL也就自然而然变成必学内容中的一部分了。
和Apache一样,MySQL同样是一个庞然大物,想用一节的文字就把MySQL完全说清楚同样是不大现实的事情,因此在本节中我们只对MySQL本身做简单介绍,主要介绍如何使用phpMyAdmin工具来管理MySQL数据库。
首先,我们来简单介绍一下MySQL数据库。和本书中所介绍的其他组件一样,MySQL是开源而且免费的,除此之外,它还有以下几个主要的优势和特点。
1. 稳定性
对于数据库来说,稳定性毫无疑问是最重要的。对于MySQL的稳定性,其实无须多虑,作为目前全球最受欢迎的开源数据库,MySQL被无数的互联网应用所采用,比如Facebook等。而在这些成功的实例中,MySQL扮演着最稳定的数据存储后盾的角色。
2. 高性能
支持多线程,性能佳,同时MySQL还提供了非常丰富的性能配置选项(在配置文件my.cnf中)。我曾经对目前Linux上的多个主流数据库做过高并发的压力测试,MySQL的处理能力绝对是名列前茅的。
3. 灵活性
单台MySQL服务器支持的对象数达到十亿(Billion)级别,因此从理论上来讲,在性能没有下降的前提下,我们可以建立任意多个数据库,每个数据库中包含任意多张数据表,这样我们就可以在一台MySQL服务器上模拟分库分表,当然,我们甚至还可以在一台服务器上建立多个MySQL实例。
4. 支持主从
主从复制(Replication)也是MySQL最重要的特性之一,MySQL支持一主多从,以及互为主从两种模式。我们常用的是一主多从的方式,在主从模式运行时,主库会持续地把数据同步到从库上去,一般来说我们会将主库作为写库而从库作为读库,这样做的好处是:多个从库不仅可以为主库分担读的压力,而且还可以为主库提供多套数据备份,当主库出问题时,我们可以通过修改配置快速地进行数据恢复。
5. 支持集群
在MySQL 5之后也支持使用NDB Cluster存储引擎来实现多Cluster的服务器集群,但是在PHP项目中通常依靠程序逻辑来实现数据库集群的功能。
6. 插件丰富
据我了解MySQL的插件应该是目前所有数据库中最多的,针对各种不同的使用场景,都会有不同的数据库引擎或者数据库插件与之对应,比如近几年出现的MySQL的NoSQL处理引擎HandleSocket等。丰富的插件系统也使得MySQL的应用范围越来越广。
接下来,我们来看看在Xampp环境下如何方便地管理MySQL。在3.2.2节中曾经提到过Xampp自带的phpMyAdmin管理工具,此工具是由纯PHP写出来的,特点就是部署完之后可以直接在浏览器中打开操作,界面如图3-12所示。
图3-12展示的就是phpMyAdmin的主界面(在不同的版本里phpMyAdmin的界面表现可能会稍有不同,但是功能布局肯定是不会变的),左边灰色的列表就是目前所有的MySQL数据库列表,其中除了mysql、information_schema、performance_schema以及test是MySQL自带的数据库之外,其他的数据库都是后来添加的。我们单击对应的数据库名就可以进入对应的数据库管理界面,例如我们单击cdcol数据库,会看到如图3-13所示的管理界面。
从图3-13中可以看到,cdcol库中只有一个表cds,单击表名就可以在右边看到表里所有数据的列表,当然我们可以对这些数据进行增删查改等动作。另外,在数据列表上面我们可以看到所有操作的相关SQL,非常方便;SQL栏上方还有一排按钮选项,这些选项的功能也是我们在日常操作中经常使用的,下面简单介绍一下。
图3-12phpMyAdmin主界面
图3-13phpMyAdmin数据表管理
浏览:默认的功能,用于管理表中的数据。
结构:用于查看表的详细结构,还可以添加索引。
SQL:使用我们自己编写的SQL语句进行数据表操作。
搜索:快捷地使用模糊搜索查找数据。
插入:插入新的数据。
导出:导出表中的数据,一般用于数据备份或者转移;phpMyAdmin提供了非常多的导出方式和选项,一般来说MySQL导出的文件都是文本SQL文件。
导入:和导出相反的功能,一般用于数据恢复。
操作:提供一些其他的高级功能选项,比如修改数据表名、修改存储引擎、修改字符集等操作,需要了解更多信息请进入相应界面查看。
清空:清空表内所有数据,此操作在未开启事务的情况下不可恢复,请慎用!
删除:删除整张表,请慎用!
由于篇幅限制,对于phpMyAdmin的一些主要功能的介绍到此为止,如果你想熟悉这个工具建议动手操作一下,熟悉一下这个MySQL管理工具的日常功能,这对后面的服务端开发是非常重要的。
3.3使用JSON通信
实际上,第1章中介绍如何结合Android和PHP学习时,我们就曾经提到过JSON协议,本节我们就来学习一下这个协议的基本内容。JSON是JavaScript对象表示法(JavaScript Object Notation)的简称,JSON协议源自JavaScript脚本语言的对象持久化表示方法,由于这种表示法比较简单易懂,而且传输的数据也比较小巧(相对于XML来说应该算是非常小巧了),因此,近年来被广泛地用于互联网应用的数据封装。
首先,我们来学习一下JSON协议的数据表示方法。在JSON协议中,最基本的数据结构只有两种。第一种是数组结构,该结构类似于PHP中的列表数组,结构如下。
["james","iris"]
第二种是对象结构,该结构非常类似于PHP中的散列数组,结构如下。
{"id":1,"name":"james"}
当然,将以上两种结构结合起来就可以产生其他形式的数据结构,比如对象数组,也就是类似于PHP中的“散列数组列表”的形式,结构如下。
[
{"id":1,"name":"james"},
{"id":2,"name":"iris"}
]
另外,JSON协议几乎支持所有主流语言的客户端,当然也包括PHP语言。在PHP中使用JSON非常方便,在PHP 5.2版本之后,PHP语言已经内置了JSON的加解码函数,即json_encode和json_decode。接下来,让我们来分析一下代码清单3-14中的逻辑代码。
代码清单3-14
<?php
// 原始数据
$arr = array(
array(
"id" => 1,
"name" => "James"
),
array(
"id" => 2,
"name" => "Iris"
)
);
// 数组转换为JSON格式
$str = json_encode($arr);
echo "Array => JSON : ".$str."\n";
// JSON转换为数组格式
echo "JSON => Array : ";
$arr = json_decode($str);
print_r($arr);
?>
以上代码演示了如何使用PHP内置的加解码函数来进行JSON数据和PHP数组结构之间的相互转换,运行结果如图3-14所示。
图3-14代码清单3-14的运行结果
这里随便提一下,在Android中我们使用org.json包来进行JSON加解码工作,JSON数组格式可使用JSONArray类处理,而对象结构则使用JSONObject类处理。关于Android使用JSON的具体使用方法和实例我们将在本书第7章的7.3.3节中做详细介绍。
3.4常用PHP开发框架
随着互联网应用的发展,我们过去依靠原生PHP代码进行编程的路已经走不通了。因为,随着代码逻辑越来越复杂,如果没有一个好的框架来管理和组织代码,乱七八糟的巨量代码最终必将把整个项目毁掉,这也是我们在项目结束之后还要花那么大的资源和精力对代码进行不断优化和重构的原因。当然,如果我们在项目开始时就采用一个比较好的框架进行开发,不仅可以让以后的功能扩充变得更加简单,还可以为日后的代码维护减少工作量。目前市面上流行的PHP框架非常多,接下来我们会对其中比较有代表性的几个框架做一些介绍和对比。
1. Zend Framework(http://framework.zend.com/)
Zend Framework简称ZF,是PHP的官方框架,优点是功能强大、结构松散、封装完善,很适合作为二次开发的基础框架,它包含了几乎所有你能想到的关于互联网方面的功能类库。当然,ZF的类库包也很大,框架类库就将近20MB,当然其中大部分我们是用不到的。虽然ZF看起来比较“笨重”,但是它所提供给开发者的自由度是其他框架所不能比较的,针对那些需求比较灵活或者结构比较复杂的大型项目而言,ZF确实不失为一个很好的选择。
笔者建议大家去深入学习ZF框架,最好是能把ZF的主要逻辑和类库的代码读一遍,因为从中你不仅可以学到很多PHP语言的编程技巧,还可以学到很多有用的“设计模式”以及“封装”的技巧,这些知识和经验都会对大家今后的编程之路大有裨益。
此外,本书实战篇中的项目实例将会使用一个基于ZF的框架Hush Framework来进行开发,因此学习ZF对大家来说也是非常必要的。当然,由于本书的篇幅限制,我们不可能在这里把ZF全部给大家讲一遍,但是我们在3.6节中在对Hush Framework进行讲解时会涉及ZF框架的一些内容,建议大家认真阅读和体会,相信其中的知识会对深入学习ZF框架有很大帮助。
小贴士:本书的“封装”指的是一种基于面向对象思想的组织代码的方法,后面会多次提到这个概念,大家在阅读时可以注意一下。
2. CodeIgniter(http://codeigniter.com)
CodeIgniter(CI)也是一个比较老牌的PHP框架,和ZF相反,它非常小巧,核心类库仅有1MB左右,使用起来比较简单,代码框架遵循常见的MVC结构;但是CI的类库封装得还不够精细,某些框架层次感觉设计得过于繁琐;另外,我认为CI的文档做得不是很好,特别是中文的文档,当然这可能是多种原因造成的,但是不可否认的是,这个问题大大阻碍了CI框架在国内的普及。
3. CakePHP(http://cakephp.org/)
CakePHP是一个典型的仿RoR类型的框架,它的脚手架(scaffold)功能还不错,也非常的小巧,类库相对来说设计得主流化一点,但是模板支持部分还做得不够好,另外系统设计的耦合度比较高,如果遇到一些大型项目会有点棘手,比如需要分库分表,以CakePHP目前的做法会比较麻烦。
4. ThinkPHP(http://www.thinkphp.cn/)
ThinkPHP是必须要介绍的,是近几年出现的比较优秀的产品之一。整体来说,ThinkPHP很快,几乎是所有PHP框架中最快的;也很小巧,所有的类包加起来才几百KB;在设计方面相对比较松散,易于学习;另外,文档也相当完善,确实是近年来出现的一个不错的PHP框架,但是面对大型项目同样有一些不方便的地方。
当然,面对如此多的PHP框架,到底应该学习哪一个呢?我们可以这样考虑:如果你只想快速地开发出一个应用,那么可以选择Think、Cake或者CI等敏捷型的开发框架;但是,如果你想要更深入地学习PHP语言,甚至PHP中的各种设计模式,请选择ZF。当然,在后面的章节中,我们将会向大家介绍一个基于ZF和Smarty的PHP开发框架,也就是Hush Framework。
3.5认识Smarty模板引擎
如果你说学过PHP而没学过Smarty模板引擎,我相信所有的面试官都会觉得你在撒谎。虽然PHP语言本身就可以嵌入到HTML页面中去进行数据展现,但是这样做我们不仅需要书写大量的<?php ?>标签,而且在某些地方还需要嵌入大量的冗余代码,另外也不利于逻辑的解耦和分离。所以,在项目中我们还是需要一个专门的模板引擎,而Smarty就是PHP语言在这个领域的不二选择了。
目前,最新的Smarty版本已经出到3.x,应该说与2.x版本相比做了很大的改进,接下来简单介绍一下Smarty的使用。实际上,Smarty的下载包中本来就包含了一些实例代码。首先,从官方下载地址(http://www.smarty.net/download)下载最新的稳定的开发包版本(Latest Stable Release),我们在这里使用的是Smarty 3.1.5版本,该版本必须运行于PHP 5.2以上的版本中。
解压之后,我们把Smarty-3.1.5重新命名为smarty并放入我们前面配置好的站点目录,然后在浏览器中打开demo地址(http://php-demo/smarty/demo/),打开的界面如图3-15所示。
图3-15Smarty demo的运行效果
小贴士:如果你找不到站点目录,请返回查看3.2.3节中Apache配置虚拟主机的部分内容。
以上这个界面就是由Smarty模板引擎渲染出来的页面,其对应的PHP文件的代码,见代码清单3-15,已添加注释,方便读者阅读。
代码清单3-15
<?php
// 包含 Smarty 类库
require('.../libs/Smarty.class.php');
// 初始化 Smarty 对象
$smarty = new Smarty;
// 初始化 Smarty 配置
//$smarty->force_compile = true;
$smarty->debugging = true;
$smarty->caching = true;
$smarty->cache_lifetime = 120;
// 各种变量赋值
$smarty->assign("Name","Fred Irving Johnathan Bradley Peppergill",true);
$smarty->assign("FirstName",array("John","Mary","James","Henry"));
$smarty->assign("LastName",array("Doe","Smith","Johnson","Case"));
$smarty->assign("Class",array(array("A","B","C","D"),array("E","F","G","H"),
array("I","J","K","L"),array("M","N","O","P")));
$smarty->assign("contacts", array(array("phone" => "1", "fax" => "2", "cell"
=> "3"),array("phone" => "555-4444", "fax" => "555-3333", "cell" => "760-1234")));
$smarty->assign("option_values", array("NY","NE","KS","IA","OK","TX"));
$smarty->assign("option_output", array("New York","Nebraska","Kansas","Iowa",
"Oklahoma","Texas"));
$smarty->assign("option_selected", "NE");
// 渲染相应模板
$smarty->display('index.tpl');
?>
从上面的代码我们可以很清晰地看到Smarty的一般使用过程:先初始化Smarty对象,然后配置Smarty参数,接着就是进行各种变量的赋值,最后在模板页面展现出来。对于本例,大家可以看看index.tpl中的模板写法,当然Smarty的用法还是很丰富的,想学好它,最好的老师就是官方文档了,请参考“http://www.smarty.net/docs/en/”。以下我们也简单介绍几种在开发时需要掌握的核心要点。
1. 常用配置选项
在使用Smarty模板引擎之前,我们必须先学习如何配置Smarty的选项。而在Smarty的常见选项中,我们首先必须了解4个最基本的目录选项。
模板目录(template):本目录用于存储模板文件,需要渲染对应文件时把文件相对地址作为参数传入display方法即可。比如,我们有一个模板文件地址位于template/test/index.tpl,那么我们则应当使用“$smarty->display('test/index.tpl');”语句来渲染该模板。
编译模板目录(template_c):本目录主要用于存储Smarty模板引擎产生的模板编译文件,Smarty也正是使用这种方法来提高执行效率的。当然,我们在部署项目时一定要注意该目录必须是可写的。
缓存目录(cache):Smarty允许把展示过的模板缓存起来,使用此功能将进一步提高模板引擎的运行速度。当然,我们还可以通过设置cache_lifetime属性来控制缓存文件的有效时间。
配置目录(configs):这个目录可以用于保存Smarty模板引擎的配置文件,不过在实际项目中使用得比较少,我们经常会把配置放入项目统一的配置目录。
在实际项目中我们经常使用继承和重载的方式来定制和配置我们自己的Smarty模板类。比如,在代码清单3-16中,我们就实现了一个自定义的My_Smarty类,此类中设置了Smarty模板的必要目录和缓存的生效时间。
代码清单3-16
<?php
// 包含 Smarty 类库
require 'Smarty.class.php';
// 定义自己的模板类
class My_Smarty extends Smarty
{
function __construct()
{
// 重载 Smarty 基类
parent::__construct();
// 配置目录
$this->setTemplateDir('/path/to/templates/');
$this->setCompileDir('/path/to/templates_c/');
$this->setConfigDir('/path/to/configs/');
$this->setCacheDir('/path/to/cache/');
// 配置缓存
$this->caching = true;
$this->cache_lifetime = 60;
// 设置默认变量
$this->assign('app_name', 'My App');
}
}
?>
在上述代码中,setTemplateDir方法用于设置模板目录,setCompileDir方法用于设置编译过的中间模板目录,setConfigDir和setCacheDir方法分别用于设置Smarty模板的配置文件和缓存文件的目录。
2. 常用模板语法
Smarty 3.0中的语法实际上和PHP的语法已经比较接近了,使用起来相当方便。接下来让我们来熟悉一下Smarty模板语言的基本用法。首先,我们要知道所有的Smarty的默认界限符号是大括号(当然这个也是可以设置的)。因此,我们可以通过类似于“{$var}”的写法来获取Smarty变量“var”的值。其次,Smarty中为我们提供了大量的字符串辅助标签,非常方便,例如,如果需要把某个变量的首字母大写,使用方法如代码清单3-17所示。
代码清单3-17
{$articleTitlecapitalize}
另外,如果我们想把时间戳转化为需要的时间格式,使用方法如代码清单3-18所示。
代码清单3-18
{$smarty.nowdate_format}
{$smarty.nowdate_format:"%Y-%m-%d"}
此外,我们还可以使用代码清单3-19中的类似方法来过滤非法字符,避免XSS(跨站攻击)的风险。
代码清单3-19
{$articleTitleescape:'html'}
{$articleTitleescape:'htmlall'}
接下来,我们还会介绍一下在展示过程中最常用到的循环语句的写法。实际上在Smarty中有两种最常用到的循环语句写法,一种是“{section}”,另一种是“{foreach}”。现在假设我们需要循环一个散列数组列表“$userList”,散列数组中包含“id”和“name”两个字段,示例见代码清单3-20,大家可以好好理解一下。
代码清单3-20
{* 注释:使用section标签循环 *}
{section name=user loop=$userList}
ID : {$userList[user].id}
NAME : {$userList[user].name}
{/section}
{* 注释:使用foreach标签循环 *}
{foreach $userList as $user}
ID : {$user.id}
NAME : {$user.name}
{/foreach}
从上面的代码中可以看出,Smarty 3.0的foreach用法已经和PHP的语法非常类似了,既容易理解又方便实用,推荐大家使用。另外,在Smarty中注释默认使用的是“{*...*}”标签,这个也需要大家了解一下。
由于篇幅限制,Smarty模板引擎的基本使用我们介绍到这里,关于其更多的信息请大家参考官方的文档并动手实践一下,毕竟Smarty模板也是使用PHP进行服务端开发的必不可少的一项技能。
3.6开发框架简介
前面大家已经学习了PHP模板引擎Smarty的用法,也简单了解了PHP的官方框架Zend Framework,接下来本书将给大家介绍一个基于Zend Framework和Smarty之上的强大的PHP开发框架,即Hush Framework。本书后面微博实例的服务端程序也将采用该框架进行开发。
在实际项目中,我们通常要先选择一个比较适合项目特点的框架,然后,在这个框架的基础上进行开发,这个过程我们通常称为“框架选型”。其实,在之前的3.4节中我们已经介绍和分析了四种目前比较主流的PHP框架,并选定了Zend Framework为本书实例的基础框架,我们通过长时间的整理归纳和项目积累,在Zend Framework和Smarty的基础上构建出了Hush Framework这个PHP的开发框架,个人还是非常推荐大家使用的,接下来我们从几个方面给大家介绍一下这个框架。
3.6.1框架的特点和优势
从某种程度上来说Hush Framework框架的特点也可以算做它的优势,所以我们主要给大家列举该框架的几个主要特点。
1. 开发效率高
Hush Framework基本沿用了Zend Framework严谨的编码规范和优秀的框架设计,形成了别具特色的MVC结构;此外,本框架目前已经被国内多个知名网络公司所采用,经过了多个实际项目的考验和提炼已经日渐成熟;另外,此框架相对比较适合国内程序员的思路,从而提高了开发效率。
2. 运行效率高
虽然Zend Framework的运行效率一直为大家所诟病,但是其中最重要的原因是类库实在太庞大了,因此Hush Framework只使用了其中几个核心类库,并对其中一些效率不够高的地方进行了精简和优化,比如URL的路由逻辑优化,DB类的用法简化等,极大限度地提高了整个框架的运行效率。
3. 可扩展性高
Hush Framework的高扩展性得益于Zend Framework优秀的类库设计,松耦合的设计方法让它能快速地适应不同项目的需求,这也是为什么我们会选择Hush Framework来作为本书实例的服务端底层框架的最主要原因之一。
当然,除了以上这些基础特点之外,Hush Framework还有很多其他的很棒的特性,比如我们可以用它的基础代码来快速开发一个常见的互联网应用,还可以用其自带的工作流模块来开发ERP系统等,但是由于本书实例仅用到框架中的最基础的MVC代码框架,所以我们后面的介绍也将围绕着Hush Framework的基本用法来给大家讲解,其中我们也会穿插一些PHP编程的要点,让大家更加了解如何在实际项目中使用PHP语言来编程。
3.6.2框架的基础目录结构
想要熟悉一个框架,最好的方式莫过于从它的代码目录结构入手,下面我们先来讲解一下Hush Framework的基础目录结构,让大家对这个框架有一个整体性的认识。下面便是对这个框架主要目录的一个对照,我建议大家使用svn工具到Hush Framework的官方网站(http://code.google.com/p/hush-framework/)上把代码下载到本地来进行比对阅读,这样才会达到比较好的学习效果。
目录说明3-1
hush-framework
- hush-app实例应用程序目录
- bin可执行文件目录
- dat临时存储文件
- doc主要文档目录
- etc配置文件目录
- lib主要逻辑目录
- Ihush
- AclACL 权限逻辑类库
- App
- Backend
- Page后台 Controller 逻辑
- Remote后台 Service 逻辑
- Frontend
- Page前台 Controller 逻辑
- BpmBpm 逻辑类库
- Dao
- AppsApps 库的 Module/Dao 类库
- CoreCore 库的 Module/Dao 类库
- tpl
- backend后台模板文件
- frontend前台模板文件
- web
- backend后台 DocumentRoot(站点目录)
- frontend前台 DocumentRoot(站点目录)
- hush-lib
- Hush
- AclAcl 权限类库
- AppApp Url Dispatcher
- Auth
- BpmBpm 类库
- CacheCache 类库
- Chart图像类库
- Crypt加密类(Rsa)
- Date
- Db数据库层(Module)类库
- Debug调试类库
- Document文档类库
- Examples一些例子(主要针对 Cli 程序)
- HtmlHtml 构建类库
- Http远程访问类库
- Json
- Mail邮件收发类库
- Message消息类库
- MongoMongodb 类库
- Page页面层(Controller)类库
- Process多进程类库
- Service服务层(Service)类库
- Session
- SocketSocket 类库
- Util工具类库
- View展示层(View)类库
- hush-pmsPHP Message Server
从上述目录结构说明中,我们可以看到Hush Framework的文件目录中,主要包含以下两大目录。
1. hush-app目录
该目录下的代码是Hush Framework给我们提供的框架实例程序,是一个比较完整的互联网应用实例,包括应用前端和管理后台两大部分,我们既可以把该实例当做一个代码示例库来学习和使用,也可以把它当做一个项目的基础架构进行二次开发;另外,之前也说过了,本书实例的服务端程序就是在本框架的基础之上开发的,因此从某种意义上来说和这里的实例程序是非常相似的,所以这里应该算是本书的重点之一了,接下来我们马上会对该实例中的一些主要用法和代码进行讲解。
特别注意一下hush-app下面的etc、lib、tpl和web四个目录,因为这四个目录分别是实例应用程序的配置目录、代码目录、模板目录和站点目录,下面我们来给大家详细介绍一下。
(1)配置目录(etc)
配置目录(etc)下面放置的都是应用的配置文件,其中比较重要的包括:全局配置文件(global.config.php)主要用于设置应用的总体配置,比如路径变量、类库位置等;数据库配置文件(database.mysql.php)用于设置数据库的服务器分布和分库分表策略等;前后台配置文件(frontend.config.php和backend.config.php)分别用于配置前后台的特殊参数。
(2)代码目录(lib)
本目录是主要的公用类库和逻辑代码目录,现将其中比较重要模块的代码分目录列举如下:权限控制模块(Acl目录)主要用于前后台的RBAC权限控制;控制器模块(App目录)用于各个页面的逻辑控制,也就是MVC中的Controller部分;工作流模块(Bpm目录)用于实例后台中工作流部分的逻辑控制;可执行程序模块(Cli目录)下面都是项目可执行程序的逻辑代码,另外我们需要知道的是hush-app/bin目录下面就是可执行程序的入口;数据操作模块(Dao目录)大家应该都非常熟悉了,这里保存的是和数据库操作相关的所有逻辑,也就是MVC中的Model部分。
(3)模板目录(tpl)
此目录下还分为前台模板目录(frontend目录)和后台模板目录(backend目录),分别用于存储应用实例前后台的Smarty模板,这也就是MVC中的View部分了,这里的模板和前面所提到过的“控制器模块”中的各个不同控制器的动作逻辑(Action)相对应。
(4)站点目录(web)
这里面放的都是一些静态文件或者独立的PHP代码等,此目录也分为前台站点目录(frontend目录)和后台站点目录(backend目录),另外这两个目录也是HTTP服务器的站点配置(DocumentRoot)所需要指定到的目录。
2. hush-lib目录
此目录保存的是Hush Framework的源代码,我们可以看到这里的代码目录和Zend Framework的结构非常一致,也就是把每个独立的模块代码都放在各自的目录下并尽量互不关联,这也比较符合“松耦合”的设计原则,既便于理解又便于阅读,是一个比较值得提倡的代码封装方法。
至此,已经给大家介绍了Hush Framework框架和应用(hush-app)中的重要代码目录,这是学习如何使用Hush Framework进行开发的重要一步,希望大家能好好消化一下以上内容。至于框架类库(hush-lib)源码中的模块和目录介绍,由于篇幅原因这里就暂时不做介绍了,有兴趣的读者可以访问Hush Framework在Google Code的官方站点,查找更多信息。
3.6.3框架MVC思路讲解
首先,Hush Framework是一个标准的MVC框架,MVC的概念大家应该都耳熟能详了,由于关系到本书重要的服务端底层框架的学习,我们还是不得不老调重弹。MVC是模型(Model)、视图(View)和控制器(Controller)的缩写,是目前业内最主流且应用最广泛的软件设计模式之一,MVC具备以下主要优点。
1. 低耦合性
MVC最大的好处之一就是大大降低了程序的耦合性,特别是把视图层和业务逻辑分开之后,让整个应用灵活了很多;当业务逻辑有变化时,我们只需修改模型层和控制器的代码,而无须修改负责应用外观的视图层。
2. 高重用性
MVC的高重用性主要体现在两方面。一方面是视图层的重用性,因为在实际的项目中,我们经常需要修改应用外观来满足需求,由于业务逻辑已经分离出来,所以我们不需要更改逻辑就可以调整应用界面,满足了软件高重用性的要求。另一方面,业务逻辑被分为模型层和控制器层之后,既保证了核心数据结构的稳定性,也增强了业务逻辑控制器的灵活性,这样我们就可以很好地重用核心数据结构来实现多种多样的业务逻辑。
3. 可维护性
MVC设计模式把模型、视图和控制器分开,实际上也把设计者、程序员和UI设计人员的职能做了分离。这种分工方式不仅可以让应用的架构看起来更清晰,还便于软件维护时团队之间的共同协作,这对中大型软件应用程序的代码维护工作将起到很重要的作用。
之前讨论的是MVC设计模式中比较通用的概念和知识,然而对于不同的框架来说,具体的实现方式却有各自不同的特色,接下来我们就来学习Hush Framework中的MVC设计思路。为了让大家理解起来更容易,我们把Hush Framework处理客户端请求的完整过程通过图形的方式展示出来,如图3-16所示。
从图3-16中我们可以很清楚地看到客户端请求的整个处理过程,在一个标准的基于HTTP协议的互联网应用环境中,用户每次操作都会致使浏览器向HTTP服务器发送HTTP请求,当服务器接收到请求之后,就会调用框架程序来处理,此时Hush Framework就会接管接下来的工作。具体的处理流程一般分为以下几个步骤。
步骤1:首先,Hush Framework的请求分发器(Dispatcher)会分析客户端发送过来的HTTP请求所包含的信息,并根据请求的URL地址来指定使用相应的控制器(Controller)来处理该请求。
步骤2:接着,被指定的控制器会选择合适的模型类(Model)用于持久层数据的获取和存储,并负责处理该请求的业务逻辑。此外,我们可以看到Hush Framework的模型层是基于Zend Framework的模型层的。当然,在逻辑处理完成后,控制器还会调用视图层(View)来组合出最终的HTML代码。
图3-16Hush Framework系统处理分析图
步骤3:最后,服务器会把Hush Framework的处理结果通过HTTP协议返回给客户端程序来进行后续的处理。
通过分析我们会发现,实际上Hush Framework的MVC设计和实现的思路还是比较主流的,和大部分网络应用的MVC框架的思路也比较相似,不过其中还是有不少独特的亮点。比如Hush Framework的分发器(Dispatcher)就使用了独有的快速分发逻辑,大大提高了程序的运行效率;另外,不仅支持常见的URL路由分发模式,还支持通过“路由映射文件”这种更直观的方式来进行更精细的配置,映射文件代码如配置清单3-3所示。
配置清单3-3etc/app.mapping.ini
; URL mappings
;
; Used by Hush_App_Dispatcher class
; e.g /url/path = PageClassName::ActionName
;
/= DebugServer::indexAction
/debug/*= DebugServer::*
另外,Hush Framework的模型层使用了Zend Framework作为底层框架,沿用了其完善的DB模型层封装和方法的设计,并使用自己独特的思路封装成Hush Framework的DAO基类,让建立在基类之上的持久层操作更加简便、高效。在接下来的3.6.4节中,我们将通过实例来讲解Hush Framework持久层的用法。
3.6.4框架MVC实例分析
通过前面两节的学习,大家应该对Hush Framework框架的理论基础有了一定的认识,但是理论还是需要通过实践来证明,要真正学会如何运用该框架进行开发,光靠理论知识是远远不够的,所以在本节中我们将会围绕着框架实例中与MVC分层开发相关的实例代码为大家做进一步的讲解。下面我们会使用框架实例中的“后台登录”这个界面的完整逻辑,来给大家讲解一下在Hush Framework中我们是如何使用MVC的分层思路来进行编程的,我们先来看一下这个界面的截图,如图3-17所示。
图 3-17框架实例后台登录界面
以上是在浏览器中打开框架实例的后台站点时看到的界面,也就是后台的登录界面。这里我们可以看到浏览器中的地址是“http://hf-be/auth/”,按照前面所介绍的Hush Framework的应用目录中所提及的,对应的控制器类应位于lib/Ihush/App/Backend/Page/AuthPage.php文件中。至此,既然已经找到分析框架MVC用法的“突破口”,那么我们就从这里开始分析吧。
我们先来看看AuthPage.php文件中的AuthPage类,此类继承自Ihush_App_Backend_Page类(即整个实例应用的后台控制器基类),其中包含多个以Action为后缀的方法,分别对应于“后台登录”界面的几个逻辑,整个类的写法都是面向对象的,大家可以对照前面3.1.4节的内容理解一下,关于其中涉及的MVC分层思路,下面我们会把这三层的相关代码提取出来,分别给大家讲解一下。
1. 控制器(Controller)
控制器简单来说就是页面的逻辑,在Hush Framework中我们通常使用Page(页面)来表示通常意义的Controller,因为对于网络应用来说Page比Controller更好理解;此外,我们还需要知道Hush Framework使用的是REST格式的URL路径结构,自域名之后的URL路径第一个表示的是控制器的名称,第二个则是控制器的动作,也就是我们通常所称的Action,比如登录界面的路径为“/auth/”,其对应的逻辑就可以在AuthPage.php文件中AuthPage类的indexAction方法中找到,这里需要注意的是当前路径如果为空,我们则会用index来代替。代码清单3-21就是AuthPage类的完整实现,大家可以参考注释来阅读代码。
代码清单3-21
/**
* @package Ihush_App_Backend
*/
class AuthPage extends Ihush_App_Backend_Page
{
public function indexAction ()
{
// TODO : 默认使用index.tpl作为Action的模板
}
public function loginAction ()
{
// 用户名/密码/验证码均不能为空
if (!$this->param('username')
!$this->param('password')
!$this->param('securitycode')) {
$this->addError('login.notempty');
}
// 验证码必须是正确的
elseif (strcasecmp($this->param('securitycode'),$this->session('securitycode'))) {
$this->addError('common.scodeerr');
}
// 通过参数验证
if ($this->noError()) {
// 使用DAO类的验证方法
$aclUserDao = $this->dao->load('Core_User');
$admin = $aclUserDao->authenticate($this->param('username'),
$this->param('password'));
// 登录失败(找不到用户)
if (!$admin) {
$this->addError('login.nouser');
}
// 登录失败
elseif (is_int($admin)) {
$this->addError('login.failed');
}
// 登录成功
else {
// 是否是超级用户
$admin['sa'] = strcasecmp($admin['name'], $this->sa) ? false : true;
// 保存登录用户信息到会话
$this->session('admin', $admin);
// 跳转至首页
$this->forward($this->root);
}
}
// 登录失败则显示登录界面
$this->render('auth/index.tpl');
}
public function logoutAction ()
{
if ($this->session('admin')) {
// 用户登出,清除用户会话信息
$this->session('admin', '');
}
$this->forward($this->root);
}
}
从以上的代码中我们可以看出,在控制器类AuthPage中有三个Action方法,分别是indexAction、loginAction和logoutAction,这些方法对应的三个功能分别是“展示登录界面”、“用户登录逻辑”和“用户登出逻辑”,以上功能的基本逻辑和涉及的PHP语法这里就不做解释了,接下来我们会给大家分析一下这几个Action方法中比较重要的知识点,学习并理解这些知识点后,有助于我们分析AuthPage控制器类的代码。
(1)使用render方法展示模板
首先我们需要知道的是在Hush Framework中使用模板有两种方式:首先是默认方式,此种方式是按照“模板根目录/Controller名/Action名.tpl”这样的规则来放置的,因此对于indexAction来说我们可以根据这个规则分析出其对应模板是tpl/backend/template/auth目录下的index.tpl;另外一种方式是通过render手动设置模板,这也正是在loginAction中最后的那行代码所做的事情,可参考代码清单3-22中的写法。
代码清单3-22
// 登录失败则显示登录界面
$this->render('auth/index.tpl');
这行代码的逻辑其实非常容易理解,就如同注释中描述的一样,当用户登录失败后,程序还是应该展示出登录页面给用户重新填写登录名和密码。
(2)使用param方法获取URL参数
param方法是开发中最常用的方法之一,该方法一般用于获得GET或者POST过来的URL参数;另外,如果这个函数带两个参数,则是设置对应URL参数的值。示例代码如代码清单3-23所示。
代码清单3-23
// 获取GET或者POST过来的username参数值
$username = $this->param('username');
// 设置username参数值为james
$this->param('username', 'james');
(3)使用addError方法处理错误信息
大家都知道,在网页表单提交之后,我们会先做一些字段的判断,比如用户名和密码是否为空等,如果这些验证没有通过,就要给页面传递一些错误信息,而addError方法就是做这个事情的,比如前面实例中的代码“$this->addError('login.notempty');”就是用来显示错误信息的,另外对应的错误信息“login.notempty”我们可以在“etc/backend.errors.ini”文件中找到。
(4)使用load方法来加载DAO类
框架已经帮助我们把Controller层中如何使用Model层的方法封装好了,那就是这里所说的load方法,我们可以使用如下代码获取任意一个DAO类,如代码清单3-24所示。
代码清单3-24
$aclUserDao = $this->dao->load('Core_User');
$admin = $aclUserDao->authenticate($this->param('username'), $this->param('password'));
以上代码取自于loginAction中的部分逻辑:首先,初始化了Core_User的DAO类供我们使用;然后,使用该类中的authenticate方法判断用户是否登录成功,这个地方的逻辑我们会在下面的模型层中做详细分析。
小贴士:前面提到的DAO是数据访问对象(Data Access Objects)的缩写,该对象常被用于进行数据库层面的各种数据操作,后面我们会经常提到。
(5)使用forward进行页面跳转
在一个互联网应用中,页面跳转是再常见不过的事情了,这里我们也能找到相应的示例代码,也就是登录成功之后跳转到首页的逻辑,如代码清单3-25所示。
代码清单3-25
$this->session('admin', $admin);
$this->forward($this->root);
(6)使用session函数操控会话
会话的概念相信有些网络开发基础的朋友都应该清楚,因为HTTP是无状态的,所以我们一般会使用会话(Session)来保存用户相关的信息,在loginAction和logoutAction中我们都可以看到相关的代码,如代码清单3-26所示。
代码清单3-26
// 保存登录用户信息到Session
$this->session('admin', $admin);
// 用户登出,清除用户会话信息
$this->session('admin', '');
2. 视图层(View)
视图层主要负责的是对应控制器逻辑的展示,一般来说是由HTML语法和Smarty变量构成的。根据前面介绍的关于Hush Framework中的两种模板使用方式,我们可以“顺藤摸瓜”找到indexAction对应的模板,也就是tpl/backend/template/auth中的index.tpl模板文件,如代码清单3-27所示。
代码清单3-27
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>用户登录</title>
<link href="{$_root}css/main.css" rel="stylesheet" type="text/css" />
<link href="{$_root}css/login.css" rel="stylesheet" type="text/css" />
{literal}
<script type="text/javascript">
if(self!=top){top.location=self.location;}
</script>
{/literal}
</head>
<body>
<div class="login-body">
<div class="login-con">
<h1><img src="{$_root}img/logo_s.gif" /><span>后台管理系统</span></h1>
<div class="login">
{include file="frame/error.tpl"}
<form action="{$_root}auth/login" method="post">
<input type="hidden" name="go" value=""/>
<input type="hidden" name="do" value="login"/>
<ul>
<li>
<label>用户名:</label>
<input type="text" class="text" name="username"/>
</li>
<li>
<label>密码:</label>
<input type="password" class="text" name="password"/>
</li>
<li>
<label>验证码:</label>
<input type="text" class="text" style="width: 50px;margin-right:5px;
text-transform: uppercase;" id="securitycode"
name="securitycode" autocomplete="off"/>
<img id="securityimg" src="{$_root}app/scode/image.php"
alt="看不清?单击更换" align="absmiddle"
style="cursor:pointer" onClick="this.src=this.src+'?'" />
</li>
<li>
<input type="submit" onclick="this.form.submit();"
class="submit" value="登录" name="sm1"/>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
以上代码大部分是比较简单的HTML语法,穿插了一些Smarty的变量,比如“{$_root}”就是设置好的全局的Smarty的变量,代表项目URL的根路径,默认是“/”。另外,在该模板里我们还看到了登录表单的代码“<form action="{$_root}auth/login" method="post">”,这里我们可以发现该登录表单将会被提交至“/auth/login”路径,其对应逻辑就在前面我们分析过的控制层中AuthPage类的loginAction方法里。
3. 模型层(Model)
模型层是MVC三层中最接近数据库的一层,里面放的是数据操作的逻辑,也就是说我们常说的CRUD操作,这部分也是我们需要重点了解的。在前面的登录界面的示例中,我们了解到loginAction中使用到了DAO类Core_User里面的authenticate方法,下面我们截取Core_User里面的相关代码给大家讲解一下,见代码清单3-28。
小贴士:CRUD操作是添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)的缩写,也就是我们常说的“增删查改”方法,这几个操作基本包含了数据操作类DAO绝大部分的使用方式,后面我们也会经常提到。
代码清单3-28
/**
* @package Ihush_Dao_Core
*/
class Core_User extends Ihush_Dao_Core
{
/**
* 设置表名
* @static
*/
const TABLE_NAME = 'user';
/**
* 设置主键
* @static
*/
const TABLE_PRIM = 'id';
/**
* Initialize
*/
public function __init ()
{
$this->t1 = self::TABLE_NAME;
$this->t2 = Core_Role::TABLE_NAME;
$this->rsh = Core_UserRole::TABLE_NAME;
// 绑定常用CRUD操作
$this->_bindTable($this->t1);
}
/**
* 登录验证方法
* @uses Used by user login process
* @param string $user 用户名
* @param string $pass 密码
* @return bool or array
*/
public function authenticate ($user, $pass)
{
$sql = $this->select()
->from($this->t1, "*")
->where("name = ?", $user);
$user = $this->dbr()->fetchRow($sql);
if (!$user['id'] !$user['pass']) return false;
if (strcmp($user['pass'], Hush_Util::md5($pass))) return $user['id'];
$sql = $this->select()
->from($this->t2, "*")
->join($this->rsh, "{$this->t2}.id = {$this->rsh}.role_id", null)
->where("{$this->rsh}.user_id = ?", $user['id']);
$roles = $this->dbr()->fetchAll($sql);
if (!sizeof($roles)) return false;
foreach ($roles as $role) {
$user['role'][] = $role['id'];
$user['priv'][] = $role['alias'];
}
return $user;
}
...
}
接下来,我们来分析一下Core_User类中使用到的几个功能要点,并以此为实例给大家介绍一下Hush Framework中模型层的核心用法,也就是框架DAO基类中已经封装好的数据库常见操作的编码和使用。
(1)DAO类的初始化
在Hush Framework中使用DAO类,首先需要配置一个和数据表相对应的DAO类,这个过程我们通常称为DAO类的初始化。其实配置一个DAO类是非常方便的,因为框架DAO基类已经帮我们封装好了绝大部分DAO类所需要的逻辑和方法,所以初始化起来非常简单。代码清单3-29就是一个最简单的DAO类的范例模板。
代码清单3-29
// DbName为数据库名
// TableName为数据表名
class DbName_TableName extends Dao_DbName {
// 配置表名
const TABLE_NAME = ' TableName ';
// 配置主键名
const TABLE_PRIM = 'PrimaryKey';
// 初始化操作
public function __init () {
// 绑定常用的CRUD操作
$this->_bindTable(TABLE_NAME);
}
}
我们可以看到,区区几行代码就已经把一个DAO类写好了。以上代码中的DbName表示数据库名,TableName表示表名,PrimaryKey则表示主键名,__init是初始化方法,__bindTable主要用于绑定CRUD方法,也就是说,初始化之后我们就可以直接使用这个DAO类来进行“增删查改”操作了。
(2)DAO类中的查询方法
查询应该是数据库最主要的用途之一,这里我们会重点讲解在Hush Framework的DAO类中使用查询的要点。从前面提到的Core_User数据操作类中的authenticate方法中我们可以看到在DAO类中经常使用到的查询(select)方法的使用范例,包括普通查询和表关联查询,示例见代码清单3-30。
代码清单3-30
…
// 普通查询
$sql = $this->select()
->from($this->t1, "*")
->where("name = ?", $user);
$user = $this->dbr()->fetchRow($sql);
…
// 表关联查询
$sql = $this->select()
->from($this->t2, "*")
->join($this->rsh, "{$this->t2}.id = {$this->rsh}.role_id", null)
->where("{$this->rsh}.user_id = ?", $user['id']);
$roles = $this->dbr()->fetchAll($sql);
在Hush Framework中,我们可以使用和Zend Framework类似的方式来“拼装”数据库SQL查询语句,其代码语法还是比较容易理解的,我们可以把其中的select方法、from方法、where方法以及join方法分别理解为SQL语句中的SELECT、FROM、WHERE以及JOIN这几个关键词,理解起来会更加清晰。当然除了以上这几个方法之外,框架底层还提供了LIMIT、GROUP BY和ORDER BY等常用SQL语句的对应方法。比如代码清单3-31中例举的就是一些相对复杂的SQL语句所对应的PHP代码的写法。
代码清单3-31
// 对应标准SQL:
// SELECT COUNT(id) AS count_id
// FROM foo
// GROUP BY bar, baz
// HAVING count_id > "1"
$select = $db->select()
->from('foo', 'COUNT(id) AS count_id')
->group('bar, baz')
->having('count_id > ?', 1);
// 对应标准SQL:
// SELECT * FROM round_table
// ORDER BY noble_title DESC, first_name ASC
$select = $db->select();
->from('round_table', '*')
->order('noble_title DESC')
->order('first_name');
当然,我们需要理解Hush Framework的这种使用方法来替代SQL语句的做法,因为对于不同的数据库,查询语句区别是比较大的,如果没有一个很好的通用SQL语句的引擎很难做到良好的通用性,然而这却恰恰是本框架的优势所在;正是因为有底层的Zend_Db来提供强大的基础,才能让Hush Framework的模型层运转得更加得心应手。为了说明这点,我们以代码清单3-32为例,可以看到同样的DAO查询语句在不同的数据库中被解释成了不同的SQL;这样我们就不需要关心应用所使用的数据库类型,简便地写出通用型的代码,大大提高了模型层代码的重用性。
代码清单3-32
// 在 MySQL/PostgreSQL/SQLite 中,对应 SQL 如下:
// SELECT * FROM foo
// ORDER BY id ASC
// LIMIT 10
//
// 在 Microsoft SQL 中,对应 SQL 如下:
// SELECT TOP 10 * FROM FOO
// ORDER BY id ASC
$select = $db->select()
->from('foo', '*')
->order('id')
->limit(10);
此外,我们还需要注意一点,Hush Framework中的数据库类都是支持读写分离的,因此这里我们使用“dbr()->fetchRow(...)”方法(dbr是只读数据库db-read的缩写)来表示从“读库”中获取内容,一般来说数据查询操作中的绝大部分情况都会使用此方法;当然与之相对的,如果我们要写入数据的话,则应该使用dbw方法来操作“写库”,比如“dbw()->delete(...)”就是在“写库”中删除信息的写法。
(3)DAO类中的CRUD方法
前面我们已经介绍了DAO类中查询操作的用法,以及Hush Framework中对于数据库读写分离用法的使用要点,对于查询操作来说我们应该使用读库,但是对于CRUD中的其他几种操作来说就应该使用写库了,也就是使用“dbw()”方法进行调用,下面我们把除了select之外的几种方法给大家介绍一下。
create方法:此方法用于创建数据,只要传入的是包含数据的散列数组,我们就可以在数据表中添加一条记录。这里需要注意的是,我们在CRUD方法中传递的数据格式经常是类似“array(key1=>value1,key2=>value2...)”格式的数组,key是键名,对应的是数据表的字段名,而value则是数据,代表的是对应键名的数据。
exist方法:此方法用于来检测数据是否存在,一般来说我们可以传入主键值进行判断,当然如果我们需要根据其他字段的值来进行判断也是可以的,只需要在第二个参数传入对应字段名即可。
read方法:此方法也是和主键相关的,用于读取与对应主键相关的数据行。因为此方法不需要组装SQL,使用起来比select方法简单许多,所以在获取与主键有关的数据行的情况下我们常用它来替代select方法。如果不使用主键,我们也可以在第二个参数传入对应字段名。
update方法:此方法用于更新数据行,既可直接传入带主键的数组进行更新(此种情况将会按照主键值更新对应数据行)。当然,我们也可以在第二个参数传入where语句进行更新。
delete方法:此方法用于删除数据行,与前面的update方法类似,我们既可直接传入主键值进行删除(此种情况将会按照主键值删除对应行)。当然,我们也可以在第二个参数传入对应字段名。
replace方法:此方法用于替换数据行,在MySQL数据库中比较常用,一般我们替换的数据行也是和主键有关系的,或者是组合型主键。
到这里,我们已经把整个代码示例“登录界面”的逻辑介绍完了,同时也把Hush Framework中如何使用MVC的思路来进行编程的基本方法讲了一遍,现在大家应该对如何使用Hush Framework来进行开发心里有数了吧。由于Hush Framework是完全面向对象的,这里大家还可以学到许多PHP语言中的面向对象编程的技巧。当然最好的学习方法就是动手,我建议大家把框架的实例代码架设起来,然后直接动手边调试边学习,以达到“学以致用”的最佳效果。另外,关于如何获取Hush Framework框架源码以及如何部署源码实例的内容,我们会在附录A中给大家做详细介绍。
3.7小结
本章中我们比较全面地介绍了使用PHP语言进行开发的几个方面。从最基本的PHP语法、语言特点,到PHP面向对象编程思路、常用开发环境的介绍,再到PHP配套开发组件(Apache和MySQL等)和主流开发框架的分析和使用;如果大家能够把本章所介绍的这些知识全部掌握,那么可以说我们就已经具备了使用PHP语言进行互联网项目开发的基本条件,接下来还需要进一步学习的就是实战经验了。
前面我们已经讨论过“为何学”的问题,大家应该对Android加PHP这套应用开发解决方案有了大致的了解。接下来介绍“如何学”的问题,由于本书的内容比较广泛,既涉及客户端开发的技术也包含很多服务端开发的内容,所以在正式开始学习本书之前,先搞清楚应该使用什么样的学习方法比较有效是非常有必要的。接下来,笔者会把这个问题分解为以下几个部分来探讨。
1.3.1如何学习Android
由于Android学习是本书最核心的内容,因此我们先来分析。由于Android应用框架是基于Java语言的,所以在学习Android之前,最理想的状态是您已经具有一定的Java语言编程基础,对Java语言的常用语法和常用类包(package)的使用也有一定的认识。当然,即使您是一名Java初学者,同样也可以从本书中学到一些非常有用的Java编程的经验。以下是Android SDK中包含的一些比较重要的Java基础类包,建议大家先自行熟悉起来。
表1-1Android SDK中的重要Java基础类包
Java类包名作用
java.ioJava普通I/O包
java.nioJava异步I/O包
java.langJava基础语言包
java.mathJava算数类包(提供高精度计算)
java.netJava基础网络接口包(URI/URL)
java.textJava文本通用接口包(DateFormat/NumberFormat)
java.utilJava常用辅助工具包(ArrayList/HashMap)
javax.cryptoJava基础加解密工具包(Cipher)
javax.xmlJava的Xml工具类包(SAXParser)
org.apache.httpJava的Http网络工具包(HttpClient)
org.jsonJava的Json工具类包(JSONObject/JSONArray)
当然,在Android SDK中除了以上这些Java基础包之外,更多的还是Android系统本身的功能类包。当然,如果要查阅更多关于Android类包的说明文档,就需要参考Android的SDK文档了。我们可以在浏览器中打开Android的SDK里的docs/reference/packages.html网页进行查阅。想要把这里面的类包全部弄懂,必将是一个漫长而艰苦的过程。当然,假如坚持到了那一天,我相信你也已经成为Android大师了。
结合本书来讲,如果你没有任何的Java编程经验或者Android基础,那么一定要更加认真地阅读本书第2章的内容,此章不仅对Android系统框架和应用框架进行了精辟的讲解,而且结合实例让你快速熟悉Android的开发框架。接下去,在阅读完本书“实战篇”的内容并慢慢熟悉Android开发之后,还要注意学习和理解“优化篇”中关于系统优化的技巧,因为没有经过优化的系统是非常脆弱的。只有在把本书“实战篇”和“优化篇”的内容全部理解透彻之后,才能往下学习“进阶篇”的内容。总而言之,学习Android开发一定要坚持“稳扎稳打,层层递进”的学习原则,这样才能达到最佳的学习效果。
1.3.2如何学习PHP
可能很多人会认为PHP学起来比较简单,事实也确实如此,但是这并不意味着我们可以很轻易地掌握使用PHP进行服务端开发的技巧。由于服务端编程涉及的知识面比较广,除了编程语言本身,还需要和很多的服务端组件打交道,比如HTTP服务器、缓存中间件、数据库等,所以我们也需要做好“刻苦学习”的准备。
如果你没有任何PHP开发基础,请认真阅读本书第3章,因为该章能够让你快速地掌握PHP语言的基础知识,以及在开发中比较常见的服务端组件的使用方法。接下来,当你看完本书第6章之后,我相信你应该会对如何使用PHP进行移动应用的服务端开发有了相当的认识。另外,和学习Android开发一样,我们同样要重视“优化篇”中关于PHP语言以及服务端优化的技巧,相信这些内容会让你的PHP编程技巧甚至服务端架构的功力更进一步。
在学习PHP的过程中一定要注意的是,要善于使用PHP的文档资源,最好是边学习、边动手、边查文档。另外,笔者一直认为PHP语言文档的完备程度是可以和大名鼎鼎的MSDN相比的。最后,要充分利用如下PHP的文档资源。
官方中文文档:http://www.php.net/manual/zh/
官方扩展库:http://pecl.php.net/packages.php
1.3.3同时学好Android和PHP
也许在以前,同时学习Android系统和PHP语言是一件很不可思议的事情,但是,在有了本书之后,同时学好这两种主流的技术不再只是一个梦想。当然,我们更不用怀疑,能同时学好Android和PHP两种技术绝对是一件一举两得的好事!
首先,编程的技术其实是相通的,每门编程语言都有自己的优势和缺点,就拿Java和PHP来说,良好的类库设计和面向对象思想是Java的优点,那么在学习的时候我们就应当思考如何把这些优点运用到PHP的程序设计中去;而简单方便的字符串和数组操作是PHP的优势,那么我们在学习Java的时候就需要考虑怎么把这部分的接口方法设计得更简洁一些。假如我们在学习Android和PHP的过程中,懂得使用类似以上提到的“取长补短”式的思路进行学习,不仅大大有益于我们对这两种技术的学习和运用,甚至还可以加强日后学习其他技术的能力。
其次,大家应该都知道目前市场上最紧缺的就是综合性的人才,对于移动互联网领域来说,既掌握Android客户端开发,又通晓PHP服务端编程的开发者绝对是移动互联网领域最受欢迎的技术人才之一。此外,根据笔者多年的职场经验来看,多掌握几种技术总归是一件好事,很难说在未来的哪一天就可能会派上大用场。另外,如果你对技术方面有更长远的目标,笔者也很希望本书能成为你踏上成功之路的一块踏板。
回到如何学习Android和PHP的问题上来。首先,我们需要清楚的是:Android代表的是客户端开发,而PHP涉及的则是服务端开发,要想把两者结合起来,我们必须通过一个第三方的文本协议JSON。对JSON不熟悉的朋友可以先学习一下本书3.3节的内容。另外,Android客户端开发和PHP服务端开发,使用的是两种完全不同的语言,要同时学好两者当然不是一件容易的事情。因此,在学习的时候,我们要注意采用“比对式”的方式去学习和思考Android和PHP这两套不同的知识体系;同时,我们也需要注意怎样使用JSON协议把这两套系统联合起来,形成一个整体。
总之,想要同时学好Android和PHP,不仅要求大家有比较坚实的编程基础知识,还需要注意学习和思考的方式,把两者看做一个整体来进行比对学习。本书在“准备篇”中把Android和PHP开发的基础知识讲解完之后,还会在“实战篇”中给大家安排“微博应用”作为实例进行讲解,该应用是一个把Android客户端开发和PHP服务端开发相结合的绝佳案例,大家可以边学习理解、边动手研究。如果读完本书之后,你已经对Android加PHP的这套技术解决方案了然于胸的话,那么我要恭喜你已经跨出了迈向成功的重要一步。
1.4小结
在本章中,我们实际上讨论了几个前期问题:为什么要学习Android移动互联网应用开发?为什么要使用Android和PHP的架构来进行开发?如何学习?相信现在大家都已经找到自己的答案了,那么在以下的章节中我们就要开始正式地学习如何开发了。在第2章和第3章中,我们将分别学习Android和PHP的开发基础和技巧。
第2章Android开发准备
在开始学习Android开发之前,让我们先来了解一个有趣的Android小知识:Android一词最早出现于法国作家利尔亚当在1886年发表的科幻小说《未来夏娃》中,书中将外表像人的机器起名为Android(不知道是不是和Angel同音的缘故),正因为如此,Android的商标也是一个绿色的小机器人。直至今日,大家都知道Android代表的是Google推出的开源智能移动终端操作系统。
本章将先对Android系统框架、Android应用框架以及Android应用开发过程中的几个要点做一个整体性的介绍,让大家尽快做好Android应用开发的准备工作。另外,在本章的最后两节,我们还将学会如何安装Android开发环境和Android开发的必备工具(Eclipse加ADT),并建立你的第一个Android项目,即Hello World项目,由此开始你的Android开发之旅。
2.1Android背景知识
Android是一种基于Linux平台的、开源的、智能移动终端的操作系统,主要使用于便携设备,Android操作系统最初由Andy Rubin开发,主要支持手机设备。2005年由Google收购注资,并召集多家制造商组成“开放手机联盟”对其进行开发改良,并逐渐扩展到平板电脑及其他领域,近年来逐渐成为主流的移动终端操作系统之一。
Android平台的研发队伍十分强大,包括Google、HTC、T-Mobile、高通、摩托罗拉、三星、LG以及中国移动在内的30多家产商都将基于该平台开发手机新型业务。当然,使用Android这个统一的平台进行开发,对于我们开发者来说也是一大福音,至少在软件应用的通用性方面,我们不需要过多考虑。但是,你知道吗?如此强大的Android系统实际上才刚满4周岁,从2008年9月发布的Android 1.0开始,在接下来的几年中,Android一直在以惊人的速度成长着,直到今天成为占领全球半数市场的“巨无霸”,这个成绩可以算得上是一个奇迹了。让我们通过下表来回顾一下Android的成长之路吧!
表2-1Android成长之路
版本发布时间主要改进
Android 1.52009年4月1. 拍摄/播放影片
Cupcake2. 支持立体声蓝牙耳机
3. 最新的采用WebKit技术的浏览器
4. 支持复制/粘贴和页面中搜索
(续)
版本发布时间主要改进
5. GPS性能大大提高
6. 提供屏幕虚拟键盘
7. 应用程序自动随着手机旋转
8. 短信、Gmail、日历,浏览器的用户接口大幅改进
9. 相机启动速度加快,来电照片显示
Android 1.62009年9月1. 重新设计的Android Market手势
Donut2. 支持CDMA网络
3. 文字转语音系统(Text-to-Speech)
4. 快速搜索框
5. 全新的拍照接口
6. 查看应用程序耗电
7. 支持虚拟专用网络(VPN)
8. 支持更高的屏幕分辨率
9. 支持OpenCore 2媒体引擎
10. 新增面向视觉或听觉困难人群的易用性插件
Android 2.0/2.1/2.22009年10月1. 优化硬件速度
Eclair2. 增加“Car Home”程序
3. 支持更高的屏幕分辨率
4. 改良的用户界面
5. 新的浏览器的用户接口和支持HTML 5
6. 新的联系人名单
7. 更好的白/黑色背景比率
8. 改进Google Maps 3.1.2
9. 支持Microsoft Exchange
10. 支持内置相机闪光灯
11. 支持数码变焦
12. 改进的虚拟键盘
13. 支持蓝牙2.1
14. 支持动态桌面的设计
Android 2.1/2.2.12010年5月1. 整体性能大幅度提升
Froyo2. 3G网络共享功能
3. Flash的支持
4. App2sd功能
5. 全新的应用商店
6. 更多的Web应用API接口的开发
Android 2.32010年12月1. 增加了新的垃圾回收和优化处理事件
Gingerbread2. 原生代码可直接存取输入和感应器事件
3. 支持EGL/OpenGL ES、OpenSL ES
4. 新的管理窗口和生命周期的框架
5. 支持VP8和WebM视频格式,提供AAC和AMR宽频编码,提供了新的音频效果器
6. 支持前置摄像头、SIP/VOIP和NFC(近场通信)
7. 简化界面、速度提升、优化文字输入/复制/粘贴等
8. 改进的电源管理系统、新的应用管理方式
(续)
版本发布时间主要改进
Android 3.02011年2月1. 针对平板的优化
Honeycomb2. 全新设计的UI增强网页浏览功能
3. 增加n-app purchases功能
Android 3.12011年5月1. 优化Gmail电子邮箱
Honeycomb2. 全面支持Google Map
3. 将Android手机系统跟平板系统再次合并
4. 任务管理器可滚动,支持USB 输入设备(键盘、鼠标等)
5. 支持 Google TV,可以支持XBOX 360无线手柄
6. 更加容易地定制屏幕widget插件
Android 3.22011年7月1. 支持更多屏幕尺寸的设备
Honeycomb2. 引入了应用显示缩放功能
Android 4.02012年1. 增强任务系统,人性化系统手势
Ice Cream2. 优化UI,支持自动缩放
3. 增强语音功能
4. 增强云服务
Android N.n未知继Ice Cream之后的下一版Android系统
Jelly Bean
从上表中,大家不仅可以了解Android系统的发展历程,而且可以了解Android系统在功能改进上的一些细节。另外,需要大家注意的是,考虑到对目前大部分设备的兼容性,本书下面的项目实例是在Android 2.2版本上安装/调试的。
2.2Android系统框架
在开始介绍Android应用开发之前,我们先来了解一下Android的系统框架。虽然,是否了解Android系统框架与能否进行Android应用开发之间没有任何必然的联系,但是在学习Android的过程中,这个部分内容却是必不可少的,因为能否理解Android的系统架构对于你日后能否对Android进行更深入的学习是至关重要的。首先,我们来看一张不得不说的图,也就是Google官方公布的Android的系统框架图,如图2-1所示。
从图2-1展示的Android系统架构图可以很清晰看出,Android系统分为四层:应用层、应用框架层、系统类库层和系统内核层。下面我们将对这四个层次做一些简要的分析和介绍。
1. 应用层(Applications)
应用层(Applications)是指运行于Android虚拟机上的程序,也就是开发者们平时开发的“手机应用”。在系统应用层里,我们可以通过Android提供的组件和API进行开发,从而编写出形形色色、丰富多彩的移动软件和游戏。
2. 应用框架层(Application Framework)
应用框架层(Application Framework)是Android应用开发的核心,为开发者开发应用时提供基础的API框架。当然,Android本身的很多核心应用也是在这层的基础上开发的。下面我们就来了解一下这些模块的作用(见表2-2)。
图2-1Android系统框架
表2-2应用框架层主要模块
模块名模块简介
1View System主要用于UI设计,包括列表(List)、网格(Grid)、文本框(Text)、按钮(Button)以及嵌入式Web浏览器(WebView)等
2Activity Manager负责管理应用程序中Activity的生命周期以及提供Activity之间的切换功能(Intent相关)
3Window Manager用于管理所有的窗口程序,如Dialog、Toast等
4Resource Manager提供非代码资源的管理,如布局文件、图形、字符串等
5Location Manager负责与定位功能LBS(Location Based Service)相关功能
6Content Providers提供了一组通用的数据访问接口,可用于应用程序间的内容交互,比如可以用于获取手机联系人数据等
7Package Manager Android系统内的包管理模块,负责管理安装的应用程序
8Notification Manager用于管理手机状态栏中的自定义信息等
9Telephony Manager手机底层功能管理模块,可用于获取手机串号或者调用短信功能
10XMPP Service用于支持XMPP协议的服务,比如与Google Talk通信等
以上列出的模块都是我们在应用开发中经常用到的,大家可以先熟悉一下。其中最核心的Activity Manager和View System我们将分别在2.3节和2.7节中作详细介绍。此外,其他常用的Android模块的相关内容我们也会在本书以后的章节中穿插介绍。
3. 系统类库层(Libraries)
为了支持上层应用的运行,Android会通过系统类库层(Libraries)中的一些比较底层的C和C++库来支持我们所使用的各个组件或者模块。以下列举一些比较重要的类库的功能,这个部分大家了解即可。
Surface Manager:负责管理显示与存储之间的互动,以及对2D绘图和3D绘图进行显示上的合成。Android中的图形系统实际上采用的是C/S结构,Client端就是应用程序,而Server端是Surface Flinger,Client端通过Binder向Server端的Surface Flinger传输图像数据,最终由Surface Flinger合成到Frame Buffer中,然后在屏幕上显示出来。
Media Framework:Android的多媒体库,该库支持多种常见格式的音频和视频的播放、录制等各种操作,比如JPG、PNG、MPEG4、MP3、AAC、AMR等。
SQLite:Android自带的关系数据库,可用于存储复杂数据。
OpenGL/ES:3D效果库,主要用于3D游戏开发。
FreeType:支持位图、矢量、字体等。
WebKit:Android的Web浏览器内核(和iOS一样)。
SGL:2D图形引擎库。
SSL:安全数据通信支持。
Libc:也就是Bionic系统C库,当前支持ARM和x86指令集。该库非常小巧,主要用于系统底层调用,在NDK中经常会使用到。
4. 系统内核层(Linux Kernel)
Android内核具有和标准的Linux内核一样的功能,主要实现了内存管理、进程调度、进程间通信等功能。就最新的Android内核源码树的根目录结构来看,Android 内核源码与标准 Linux 内核并无不同;但是,经过与标准 Linux 内核源代码进行详细对比,可以发现Android内核与标准Linux内核在文件系统、进程间通信机制、内存管理等方面存在着不同。当然,了解它们之间的区别对进一步了解Android系统是有很大帮助的,下面我们从几个方面来分析两者之间的异同。
文件系统。不同于桌面系统与服务器,移动设备采用的大多不是硬盘而是 Flash 作为存储介质。因此,Android 内核中增加了标准 Linux 专用于 Flash 的文件系统 YAFFS2。YAFFS2 是日志结构的文件系统,提供了损耗平衡和掉电保护,可以有效地避免意外断电对文件系统一致性和完整性的影响。经过测试证明,YAFFS2 性能比支持 NOR 型闪存的 JFFS2 文件系统更加优秀。YAFFS2对Nand-Flash芯片也有着良好的支持。
进程间通信机制。Android 增加了一种进程间的通信机制 IPC Binder。Binder 通过守护进程 Service Manager 管理系统中的服务,负责进程间的数据交换。各进程通过 Binder 访问同一块共享内存,以达到数据通信的机制。从应用层的角度看,进程通过访问数据守护进程获取用于数据交换的程序框架接口,调用并通过接口共享数据,而其他进程要访问数据,也只需与程序框架接口进行交互,方便了程序员开发需要交互数据的应用程序。
内存管理。在内存管理模块上,Android 内核采用了一种不同于标准 Linux 内核的低内存管理策略。Android 系统采用的是一种叫作 LMK(Low Memory Killer) 的机制,这种机制将进程按照重要性进行分级、分组,内存不足时,将处于最低级别组的进程关闭,保证系统是稳定运行的。同时,Android 新增加了一种内存共享的处理方式 Ashmem(Anonymous Shared Memory,匿名共享内存)。通过Ashmem,进程间可以匿名自由共享具名的内存块,这种共享方式在标准 Linux 当中也是不被支持的。
电源管理。由于 Android 主要用于移动设备,电源管理就显得尤为重要。不同于标准Linux内核,Android 采用的是一种较为简单的电源管理策略,通过开关屏幕、开关屏幕背光、开关键盘背光、开关按钮背光和调整屏幕亮度来实现电源管理,并没有实现休眠和待机功能。目前有三种途径判断调整电源管理策略:RPC调用、电池状态改变和电源设置。系统通过广播 Intent 或直接调用 API 的方式来与其他模块进行联系。电源管理策略同时还有自动关机机制,当电力低于最低可接受程度时,系统将自动关机。另外,Android 的电源管理模块还会根据用户行为自动调整屏幕亮度。
驱动及其他。相对于标准内核,Android 内核还添加了字符输出设备、图像显示设备、键盘输入设备、RTC 设备、USBDevice 设备等相关设备驱动,增加了日志(Logger)系统,使应用程序可以访问日志消息,使开发人员获得更大的自由。
2.3Android应用框架
前面介绍了Android的系统框架,主要目的是让大家对Android系统有整体的概念,也为日后更深入的学习打好基础。然而,目前我们更需要重点学习和掌握的则是Android的应用框架,因为是否能掌握和理解Android应用框架,直接关系到是否能学好Android应用开发。
Android的应用框架是一个庞大的体系,想要理解透彻并不是那么简单的事情,但是,好在其中有一些比较清晰的脉络可以帮助我们快速地熟悉这个系统,因此抓住这些脉络中的核心要点对于能否学好Android的应用开发来说是至关重要的。一般来说,Android应用框架中包含四个核心要点,即活动(Activity)、消息(Intent)、视图(View)和任务(Task)。
如果你觉得上述核心要点的概念很陌生,不好理解,那么我们来看看下面这个比喻:如果把一个Android应用比喻成海洋,那么每个Activity就是这个海洋中的岛屿,假设我们眼前有一项任务(也就是Task),需要我们在其中若干个岛屿上建立起自己的王国。于是问题来了,我们要怎么样从一座岛屿去到另一座岛屿呢?没错,我们需要交通工具,而Intent就是我们最重要的交通工具。当然,Intent不仅可以带我们去,而且还可以帮我们带上很多需要的东西。接着,到了岛上,我们开始建立一个自己的王国,要知道这可需要很多的资源,这个时候,我们就会想到View这个建筑公司,因为他可以帮助我们快速地建出我们需要的东西。这样,Activity、Intent、View以及Task一起配合完成了一个完整的Android应用的王国。
从以上的比喻中,我们还可以认识到,在这四个要点中,Activity是基础,Intent是关键,View是必要工具,而Task则是开发的脉络。对于开发者来说,只有掌握了Activity、Intent、View和Task这几个核心要素之后,才能够做出多种多样的应用程序。接下来,让我们分别学习一下这四个核心要点。
2.3.1活动(Activity)
活动(Activity)是Android应用框架最基础、最核心的内容和元素,每个Android应用都是由一个或者若干个Activity构成的。在Android应用系统中,Activity的概念类似于界面,而Activity对象我们通常称之为“界面控制器”(从MVC的角度来说)。从另一个角度来理解,Activity的概念比较类似于网站(Web)开发中“网页”的概念。此外,当Android应用运行的时候,每个Activity都会有自己独立的生命周期,图2-2所示的就是Activity的生命周期。
图2-2Activity生命周期
其实,在Android系统内部有专门的Activity堆栈(Stack)空间,用于存储多个Activity的运行状态。一般来说,系统会保证某一时刻只有最顶端的那个Activity是处于前端的活动(foreground)状态。也正因如此,一个Activity才会有如图2-2所示的生命周期。当一个Activity启动并进入活动状态的时候,调用顺序是onCreate、onStart、onResume;退居后台的时候,调用顺序是onPause、onStop;重新回到活动状态的时候,调用顺序是onRestart、onStart、onResume;销毁的时候,调用顺序是onPause、onStop、onDestroy。我们应该深刻理解这些状态的变化过程,因为在Android应用开发的过程中我们会经常用到。至于如何更好地掌握Activity的特性,大家可以尝试将以下代码(代码清单2-1)放入Android应用中运行,并对照程序打印出的调试信息来理解Activity生命周期各阶段的使用。
代码清单2-1
// 基础Activity类,用于测试
public class BasicActivity extends Activity {
private String TAG = this.getClass().getSimpleName();
public void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "TaskId:"+this.getTaskId());
}
public void onStart() {
super.onStart();
Log.w(TAG, "onStart");
}
public void onRestart() {
super.onStart();
Log.w(TAG, "onRestart");
}
public void onResume() {
super.onResume();
Log.w(TAG, "onResume");
}
public void onPause() {
super.onPause();
Log.w(TAG, "onPause");
}
public void onStop() {
super.onStop();
Log.w(TAG, "onStop");
}
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "onDestroy");
}
public void onNewIntent() {
Log.w(TAG, "onNewIntent");
}
}
此外,所有的Activity必须在项目基础配置文件AndroidManifest.xml中声明,这样Activity才可以被Android应用框架所识别;如果你只写了Java代码而不进行声明的话,运行时就会抛出ActivityNotFoundException异常。关于Activity声明的具体操作,我们会在2.10.2节中结合Hello World项目进行详细介绍。
2.3.2消息(Intent)
参考之前我们对Android应用框架的几个核心要点的比喻,我们应该知道Intent消息模块对于Android应用框架来说有多重要;如果没有它的话,Android应用的各个模块就像一座座“孤岛”,根本不可能构成一个完整的系统。在Android应用系统中,我们常常把Intent称为消息,实际上,Intent本身还是一个对象,里面包含的是构成消息的内容和属性,主要有如下几个属性,我们来分别认识一下。
1. 组件名称(ComponentName)
对于Android系统来说,组件名称实际上就是一个ComponentName对象,用于指定Intent对应的目标组件,Intent对象可以通过setComponent、setClass或者setClassName方法来进行设置。
2. 动作(Action)
消息基类(Intent)中定义了各种动作常量(字符串常量),其中比较常见的有:ACTION_MAIN(对应字符串android.intent.action.MAIN)表示应用的入口的初始化动作;ACTION_EDIT(对应字符串android.intent.action.EDIT)表示常见的编辑动作;ACTION_CALL(对应字符串android.intent.action.CALL)则表示用于初始化电话模块动作等。Intent对象常使用setAction方法来设置。
3. 数据(Data)
不同的动作对应不同的数据(Data)类型,比如ACTION_EDIT动作可能对应的是用于编辑文档的URI;而ACTION_CALL动作则应该包含类似于tel:xxx的URI。多数情况下,数据类型可以从URI的格式中获取,当然,Intent也支持使用setData、setType方法来指定数据的URI以及数据类型。
4.类别(Category)
既然不同的动作应该对应不同的数据类型,那么不同的动作也应该由不同的类别的Activity组件来处理,比如CATEGORY_BROWSABLE表示该Intent应该由浏览器组件来打开,CATEGORY_LAUNCHER表示此Intent由应用初始化Activity处理;而CATEGORY_PREFERENCE则表示处理该Intent的应该是系统配置界面。此外,消息对象(Intent)可以使用addCategory添加一种类型,而一个Intent对象也可以包含多种类型属性。
5. 附加信息(Extras)
一个Intent对象除了可以包含以上的重要信息之外,还可以存储一些自定义的额外附加信息,一般来说,这些信息是使用键值对(key value)的方式存储的。我们可以使用putExtra方法设置附加信息,信息类型非常丰富(一般还是以字符串为主);在接收的时候使用getExtras方法获取。
6. 标志(Flags)
除了上面提到的几个功能属性,消息基类中还定义了一系列特殊的消息行为属性(也就是标志),用于指示Android系统如何去启动Activity以及启动之后如何处理。关于标志(Flags)的使用我们还会在2.3.4节中介绍。
在Android应用中,消息(Intent)的使用方式通常有两种,一是显式消息(Explicit Intent),另一个则是隐式消息(Implicit Intent)。显式消息的使用比较简单,只需要在Intent中指定目标组件名称(也就是前面提到的ComponentName属性)即可,一般用于目标Activity比较明确的情形。比如在一个固定流程中,我们需要从一个Activity跳转到另一个,那么我们就会使用显式的消息。而隐式消息则比较复杂一点,它需要通过消息过滤器(IntentFilter)来处理,一般用于目的性不是那么明确的情形,比如应用中的某个功能需要往目的地发送消息,但是我们却不确定要使用短信发送还是微博发送,那么这个时候就应该使用隐性消息来处理了。下面是一个典型的消息过滤器的配置范例,如代码清单2-2所示。
代码清单2-2
<activity...>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" android:mimeType="image/*" />
</intent-filter>
</activity>
我们看到,配置消息过滤器使用的是<intent-filter/>标签,一般需要包含三个要素:action、category以及data。其中,action是必需的,category一般也是需要的,而data则允许没有设置。接下来,我们学习一下这几个要素的使用方法。
<action/>:在Android应用中,一般会通过<action/>元素来匹配消息(Intent),如果找到Action就表明匹配成功,否则就是还没找到目标。需要注意的是,如果消息过滤器没有指定<action/>元素,那么此消息只能被显式消息匹配上,不能匹配任何的隐式消息;相反,当消息没有指定目标组件名称时,可以匹配含有任何包含<action/>的消息过滤器,但不能匹配没有指定<action/>信息的消息过滤器。
<category/>:<category/>元素用于标注消息的类别。值得注意的是,假如我们使用<category/>元素来标识消息类别,系统在调用Context.startActivity方法或者Context.startActivityForResult方法时都会自动加上DEFAULT类别。因此,除了Intent已经指定为Intent.ACTION_MAIN以外,我们还必须指定<category/>为android.intent.category.DEFAULT,否则该消息将不会被匹配到。另外,对于Service和BroadcastReceiver,如果Intent中没有指定<category/>,那么在其消息过滤器中也不必指定。
< data/>:通过data字段来匹配消息相对来讲比较复杂,通常的data字段包含uri、scheme(content, file, http)和type(mimeType)几种字段。对于Intent来说,我们可以使用setData和setType方法来设置,对于IntentFilter来讲,则可以通过android:scheme和android:mimeType属性分别来指定,使用范例如代码清单2-3所示。
代码清单2-3
<activity ...>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" android:mimeType="image/*" />
</intent-filter>
</activity>
以上的配置表明该Activity可以发送图片,而且内容必须是单独的一个文件,也就是说,该文件的URI路径必须是以“file://”开头的。当然,如果我们把这里的“android:scheme”改成“content”的话,则表明该图片内容必须是由ContentProvider提供的,即URI必须是以“content://”开头的。
至此,我们已经介绍了消息(Intent)和消息过滤器(IntentFilter)的基本概念和用法。我们必须清楚的是,消息分为显式消息和隐式消息两种,而消息过滤器一般是提供给隐式消息使用的。Android消息过滤器的过滤规则比较严格,只要我们申明了除了默认值(DEFAULT)之外的action、category和data,那么,只有当对应消息对象的动作(action)、类别(category)和数据类型(data)同时符合消息过滤器的配置时才会被考虑。关于<intent-filter/>标签的具体使用方法,我们将会在本书7.2.4节中结合实例进行讲解。
2.3.3视图(View)
视图(View)系统主管Android应用的界面外观显示,因此也称作Android UI系统,是Android应用框架中最重要的组成部分之一。我们在Activity中展示或者操作的几乎所有控件都属于View。Android应用框架的View System包含View和ViewGroup两类基础组件。下面我们来理解一下Android视图系统的层次结构,如图2-3所示。
视图类(View)是所有视图(UI)控件(包括ViewGroup)的基类。视图组(ViewGroup)则类似于集合,一个视图组可以包含多个ViewGroup和View,类似于Html标签中的层(div)。接下来,我们再来看看View中会经常使用的一些UI控件(见表2-3),你也可以在Android SDK参考文档(Reference)中的android.widget包下找到它们。
从表2-3中可以看出,Android应用框架为我们提供了非常丰富的视图控件,从某种程度上来说,Android应用的界面是通过各种各样的视图控件组合起来的。至于这些视图控件的具体用法,我们将在第7章中结合项目实例进行介绍。
表2-3 Android主要UI控件
主要控件说明
Button普通按钮
CheckBox多选框控件
EditText编辑框控件
Gallery图片集控件
GridView格子显示控件
ImageButton图片按钮
ImageView图片控件
LinearLayout线性布局
ListPopupWindow弹出式多选框
ListView列表控件
PopupMenu弹出菜单
PopupWindow弹出窗口
ProgressBar进度条控件
RadioButton单选框控件
RelativeLayout绝对定位布局
ScrollView滚动式列表
TableLayout表格布局
TextView文本框
Toast弹出提示框
本节只是从应用程序框架组成部分的角度简单地介绍了Android UI系统的概念,关于UI系统的更多知识以及UI控件的具体用法,我们将在本章2.7节中更系统地介绍。
2.3.4任务(Task)
本节介绍Android任务(Task)的概念。区别于以上介绍的活动、消息和视图这几个要点,任务的概念显得比较抽象,且我们在日常编码过程中也不会直接接触到,但是,理解任务却是理解整个Android应用框架的关键。
首先,我们来认识一下Android系统中的任务是如何运行的。简单来说,当我们在手机的应用列表(Application Launcher)中点击某个应用图标的时候,一个新的Task就启动了,后面的操作可能会涉及多个应用中不同Activity的界面,而这些Activity的运行状态都会被存储到Task的Activity堆栈(Activity Stack)中去。和其他的堆栈一样,Activity堆栈采用的是“后进先出”的规则。图2-4展示就是一个常见任务中Activity堆栈的变化情况。
每次启动一个新的Activity,其都会被压入(push)到Activity堆栈的顶部,而每次按“BACK”键,当前的Activity就会被弹出(pop)Activity堆栈;另外,如果按了“HOME”键的话,该Task会失去焦点并被保存在内存中;而一旦重新启动,Task会自动读出并显示上次所在的Activity的界面。那么,从一个应用进入另一个应用的情况是怎样呢?比如,应用中需要配置一些系统设置,那么我们就需要考虑一下多任务切换的情况了,如图2-5所示。
图2-4单任务模式中Activity堆栈的变化
图2-5多任务模式中Activity堆栈的变化
我们假设Task A是应用A的任务,也是我们所在的任务,当运行到Activity 3的时候我们按了“Home”键,于是Task A中的所有Activity就都被停止了,同时Task A暂时退居到后台(Background);这时,我们点击应用B的图标激活了Task B,于是Task B就被推到了前台(Foreground),并展示出最上层的Activity Z;当然,我们还可以用类似的操作把Task A激活并放置到前台进行操作。以上也是我们使用Android系统最经常使用的行为操作,大家可以结合实际情况好好理解一下。
以上的策略已经可以满足大部分Android应用的需求。此外,Android还提供了一些其他的策略来满足一些特殊的需求。比较常见的,如我们可以在Android基础配置文件(Menifest File)中使用<activity/>元素的launchMode属性来控制Activity在任务中的行为特征。launchMode有以下四种模式可供选择。
Standard模式:Standard模式为默认模式,无论是打开一个新的Activity,还是接收Intent消息,系统都会为这个Activity创建一个新的实例(instance);每个Activity都可以被实例化多次,并且每个任务都可以包含多个实例。此模式最常用,但是其缺点就是太耗费系统资源。
singleTop模式:该模式下的行为和Standard模式下的行为基本相同,如果该Activity正好在运行状态(也就是在Activity堆栈的顶部),那么其接收Intent消息就不需要重新创建实例,而是通过该类的onNewIntent()方法来处理接收到的消息。这种处理方式在一定程度上会减少一些资源浪费。
singleTask模式:此模式保证该Activity在任务中只会有一个实例,并且必须存在于该Task的根元素(即栈底)。此模式比较节省资源,手机浏览器使用的就是这种模式。
singleInstance模式:此模式与singleTask模式类似,不同之处是该模式保证Activity独占一个Task,其他的Activity都不能存在于该任务的Activity堆栈中。当然,Activity接收Intent消息也是通过onNewIntent方法实现。
此外,我们还可以通过设置Intent消息的flag标志来主动改变Activity的调用方式,比较常见的flag如下。
FLAG_ACTIVITY_NEW_TASK:在新的Task中启动目标Activity,表现行为和前面提到的singleTask模式下的行为一样。
FLAG_ACTIVITY_SINGLE_TOP:如果目标Activity正好位于堆栈的顶部,则系统不用新建Activity的实例并使用onNewIntent()方法来处理接收到的消息。表现行为和前面提到的singleTop模式下的行为一样。
FLAG_ACTIVITY_CLEAR_TOP:如果目标Activity的运行实例已经存在,使用此方法系统将会清除目标Activity所处的堆栈上面的所有Activity实例。
需要注意的是,官方文档中建议多使用默认的Task行为模式,因为该模式比较简单也易于调试。对于一些特殊的需求,如果需要使用到其他模式的话,需要模拟不同的情况多进行一些测试,以防止在一些特殊情况下出现不符合预期的情况。当然,说句实话,目前主流移动设备上的Android版本都还比较旧,对多任务管理的支持和体现还不够明显,不过,我们应该可以在Android最新版本(如Android 4.0)里看到对系统任务管理功能的加强。
2.4Android系统四大组件
之前我们已经学习了Android应用框架的四大核心要点,对Android的应用框架有了一个总体性的了解,接下来我们要学习Android应用程序中的四个重要组成部分,也就是我们一般所说的“应用组件”。在前面讲解四大核心要点的篇幅中,我们曾经提到了控件(View控件)的概念,现在我们再来学习一下Android应用框架中的组件的概念。那么何谓组件呢?顾名思义,组件当然要比控件复杂,简而言之,组件是用于工业化组装的部件。要达到组件的标准,必须符合三个要求,以下我们结合Android应用框架讨论如下。
1. 有统一标准
这点应该是形成组件的前提条件,试问,组件如果没有标准,如何组装?在这点上,Android应用框架中定义了很多标准接口,满足了组件间的各种接口需求;换一种说法,整合Android系统都是按照接口规范设计出来的。
2. 可独立部署
组件应该是独立的,每个组件都有自成一套的功能体系,否则就没有形成组件的必要。比如每个Activity都是可以独立构造的,使用Activity组件,我们可以完成一个包含许多复杂功能的界面;而使用Service,我们可以操作一个独立的后台进程等。
3. 可组装整合
可组装是组件最重要的特性,一个完整的Android应用必然是若干个系统组件构成的,这就要求组件必须是能组装在一起的,至于如何组装,我们会在后面的章节中结合实例进行介绍。
通常来讲,Android应用框架中包含了四大组件:活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供者(Content Provider)。这四大组件除了具有前面所提到的三个特点之外,还有着相同的显著特点,那就是它们都可以在Android的基础配置文件,即AndroidManifest.xml中进行配置。下面我们就来学习Android系统四大组件的基本概念和使用方法。
2.4.1活动(Activity)
在2.3.1节中,我们已经介绍了Android活动(Activity)的生命周期以及基本行为,大家应该对Activity的概念有了一定的了解。此外,Activity同时还是Android系统四大组件中的一员,因此,本节将着重介绍Activity作为组件的一般声明方法。
说到Activity的声明方法,我们必须先了解Android全局配置文件AndroidManifest.xml的基础知识。每个Android应用项目都会有自己的全局配置文件,该文件包含了应用的系统常量、系统权限以及所含组件等配置信息。配置使用范例如代码清单2-4所示。
代码清单2-4
<manifest ...>
<application ...>
<activity android:name="com.app.android.HelloActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape">
<intent-filter>
...
</intent-filter>
<intent-filter>
...
</intent-filter>
</activity>
…
</application>
<uses-permission .../>
<uses-permission .../>
...
</manifest>
从上述配置使用范例中,我们可以看到在AndroidManifest.xml配置文件范例的根元素<manifest/>下面有两种标签,即<application/>和<uses-permission/>元素。前者是应用配置的根元素,而后者则用于配置应用的权限。这里顺便说一下,每个Android应用都必须事先声明应用需要的权限,比如是否需要网络、是否需要使用摄像头或者是否需要打开卫星定位(GPS)等。而用户在安装该应用之前,系统会先提示用户是否允许该应用使用这些权限,如果用户觉得应用不安全便可以选择不安装,这在一定程度上也提高了Android系统的安全性。
另外,我们还可以看到,在以上配制文件中的<application/>元素里面含有一个或者若干个<activity/>元素,这个就是我们需要重点了解的Activity标签了。首先,我们来看一下该标签内部的一些常用的配置选项。
android:name:表示该Activity对应的类的名称,在代码清单2-4中,我们就定义了一个Activity,它的具体类包名就是“com.app.android.HelloActivity”。
android:theme:表示Activity所使用的主题,在Android系统中是允许我们自定义主题的(这部分的内容我们在后面章节的实例中会介绍到),在代码清单2-4中,使用的是默认主题“@android:style/Theme.NoTitleBar.Fullscreen”,也就是全屏模式。
android:launchMode:Activity的行为模式,之前在2.3.4节中介绍过该标签的4种选项,即与任务行为有关的Standard、singleTop、singleTask以及singleInstance。
android:screenOrientation:表示屏幕的方向,在代码清单2-4中,landscape表示的是该Activity是横屏显示的,如果改成portrait的话,则就变成竖屏显示。
当然,Activity标签可配置的选项远不止以上这些,更详细的使用说明可以参考7.1.2节的内容,使用范例可参考代码清单7-11。此外,从上面的配制文件中我们还可以看到不止一个<intent-filter>元素。关于这点,实际上,前面我们已经介绍过消息过滤器的用法,如果大家有疑问的话,可以参考2.3.2节中与消息(Intent)相关的内容。
另外,Activity在应用开发中被用做控制界面的逻辑,也就是MVC中的Controller控制器,关于Android应用中MVC的概念可参考5.2.3节中的内容。开发者可以根据需要,在Activity的生命周期方法中添加不同的逻辑来控制对应应用界面的显示、动作和响应等,而Activity类的具体用法和代码示例我们可以在本书第7章的“微博实例”代码中学习到。
2.4.2服务(Service)
Android系统中的Service服务组件和Windows系统中的后台服务有点类似,这个概念应该很容易理解,比如,我们在退出某些聊天软件之后还是可以接收到好友发来的消息,就是使用Android服务组件来实现的。此外,如果需要在应用后台运行某些程序,Service服务组件也绝对是最佳的选择。另外,值得注意的是,Service和之前的Activity一样,也有自己的生命周期,但是,Service的生命周期相对简单一些,如图2-6所示。
从图2-6中我们可以看出Android服务(Service)主要有以下两种运行模式。
独立运行模式:我们一般通过“startService()”方法来启动一个独立的服务,在这种模式下,该服务不会返回任何信息给启动它的进程,进程的动作结束后会自动结束。比如,浏览器下载就属于独立服务。
绑定运行模式:与独立服务不同,绑定服务是与启动它的应用绑定在一起的,当该应用结束的时候,绑定服务也会停止。另外,这种服务可以和应用中的其他模块进行信息交互,甚至进行进程通信(IPC)。
图2-6Service生命周期
与Activity类似,onCreate和onDestroy分别是Android服务创建和销毁过程中的回调方法。与独立运行模式相比,绑定运行模式中多出来onBind和onUnbind两个函数,分别是服务绑定和解绑过程的回调方法。在Android应用开发的时候,我们通常会使用startService方法来开启Service服务。另外,在应用开发的时候千万别忘了我们必须事先在全局配置文件中进行如下声明,如代码清单2-5所示。
代码清单2-5
<application ...>
<service android:name=".HelloService"/>
<activity ...>
...
</activity>
</application>
理解Android服务(Service)时要特别注意,千万不要盲目认为服务是一个独立的进程或者线程。实际上,它和应用程序的进程之间存在着复杂的联系,所以如果我们需要在Service中做一些耗时操作的话,必须新起一个线程并使用消息处理器Handler来处理消息。另外,Android服务的进程间通信(IPC)功能还涉及AIDL(Android Interface Definition Language,Android接口定义语言),有兴趣的话尽管去了解一下。关于Service的具体使用实例,大家可以先去看看Android SDK中API Demos里面的RemoteService实现,本书后面的实例中我们也会穿插介绍。
小贴士:Handler是消息处理器,用于接受子线程的消息进行处理并配合主线程更新UI界面,具体内容可参考5.2.2节中界面基础类BaseUi的相关内容。
在Android系统中,Service服务类的使用方法比较简单,执行Service对象的start方法就可以开启一个服务。实际上,第7章的“微博实例”中也有与Service服务相关的代码实例,请参考7.5.4节。
2.4.3广播接收器(Broadcast Receiver)
广播接收器(Broadcast Receiver)是Android系统的重要组件之一,可以用来接收其他应用发出来的广播,这样不仅增强了Android系统的交互性,而且能在一定程度上提高用户的操作体验。比如,你在把玩应用或者游戏的同时也可以随时接收一条短信或者一个电话,或者你在打开网页的同时还可以接收短信验证码等。
广播接收器的使用也很简单,和其他组件的步骤一样:先声明,再调用。代码清单2-6就是一个声明广播接收器的例子。
代码清单2-6
<application ...>
<receiver android:name=".HelloReceiver">
<intent-filter>
<action android:name="com.app.basicreceiver.helloreceiver"/>
</intent-filter>
</receiver>
<activity ...>
...
</activity>
</application>
这里我们定义了一个名为HelloReceiver的广播接收器。这个类里面只有一个onReceive方法,里面我们可以定义需要的操作。使用的时候,我们可以在Activity中直接使用sendBroadcast方法来发送广播消息,这样HelloReceiver就会接收到我们发送的信息并进行相应的处理。这里需要注意的是,广播接收器也是在应用主线程里面的,所以我们不能在这里做一些耗时的操作,如果需要的话,可以新开线程来解决。发送广播消息的范例如代码清单2-7所示。
代码清单2-7
…
Intent intent = new Intent("com.app.basicreceiver.hello");
sendBroadcast(intent);
…
而接收消息的使用范例,也就是广播接收器类HelloReceiver的逻辑实现,我们可以参考代码清单2-8。
代码清单2-8
public class HelloReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Toast.makeText(context, "Receive Action : " + action, 1000).show();
}
}
另外,我们需要了解,Android系统中的广播消息是有等级的,可分为普通广播(Normal Broadcasts)和有序广播(Ordered Broadcasts)两种。前者是完全异步的,可以被所有的接收者接收到,而且接收者无法终止广播的传播;而有序广播则是按照接收者的优先级别被依次接收到。优先级别取决于intent-filter元素的android:priority属性,数越大,优先级越高。至于使用,我们通常会在onResume事件中通过registerReceiver进行注册,在onPause等事件中注销,这种方式使其能够在运行期间保持对相关事件的关注。常见的广播事件有:短信广播、电量通知广播等。
2.4.4内容提供者(Content Provider)
在Android应用中,我们可以使用显式消息(Explicit Intent)来直接访问其他应用的Activity,但是这仅限于Activity的范畴;如果需要使用其他应用的数据,还需要用到另外一种组件,这就是所谓的内容提供者(Content Provider)。
顾名思义,内容提供者就是Android应用框架提供的应用之间的数据提供和交换方案,它为所有的应用开了一扇窗,应用可以使用它对外提供数据。每个Content Provider类都使用URI(Universal Resource Identifier,通用资源标识符)作为独立的标识,格式如:content://xxx。其格式类似于REST,但是比REST更灵活,因为在调用接口的时候还可以添加Projection、Selection、OrderBy等参数,结果以Cursor的模式返回。Content Provider的声明写法非常简单,示例可参考代码清单2-9。
代码清单2-9
<application ...>
<provider android:name="com.app.android.HelloProvider"
android:authorities="com.app.android.HelloProvider"/>
<activity ...>
...
</activity>
</application>
关于Content Provider的类实现,我们只需要继承ContentProvider接口并实现其中的抽象方法即可,这几个方法有点类似于数据操作对象DAO的抽象方法,其中包括insert、delete、query和update这些常见的“增删查改”的接口方法。对于具体的数据存储来说,一般会使用Android的内置数据库SQLite,当然也可以采用文件或者其他形式的混合数据来实现。关于Android系统中的数据存储我们会在2.6节中介绍。
我们在使用上述四大组件的时候还需要注意的是:实际上,Service和Content Provider都可用于IPC(Inter-Process Communication,进程间通信),也就是在多个应用之间进行数据交换。Service可以是异步的,而Content Provider则是同步的。在某些情况下,在设计的时候我们要考虑到性能问题。当然,Android也提供了一个AsyncQueryHandler帮助异步访问Content Provider。关于以上四大组件的具体使用,我们会在后面的章节中穿插介绍。
另外,与Content Provider配合使用的还有Content Resolver,即内容处理器。前面也提到了Content Provider是以数据库接口的方式将数据提供出去,那么Content Resolver也将采用类似的数据库操作来从Content Provider中获取数据,而获取数据就需要使用query接口。和Content Provider类似,Content Resolver也需要使用URI的方式来获取对应的内容,其使用范例可参考7.3.2节中提到的Httputil类的相关代码(代码清单7-34)。
2.5Android上下文
大家对上下文(Context)的概念并不陌生,在软件开发领域,它主要用于存储进程或应用运行时的资源和对象的引用,此外,我们在接触其他系统和框架的时候也经常会碰到上下文的概念。当然,对于Android应用来说,上下文是非常重要的,这部分的内容在Android应用的实际开发中也会经常使用到,因此本节将会重点介绍Android上下文的相关知识,为后面实战编程打下一定的基础。
在Android应用框架中,根据作用域的不同,可以把上下文分为两种,一种是Activity界面的上下文,即Activity Context;另一种是Android应用的上下文,即Application Context。下面我们分别介绍这两种上下文的概念和使用。
2.5.1界面上下文(Activity Context)
界面上下文(Activity Context)在应用界面(Activity)启动的时候被创建,主要用于保存对当前界面资源的引用。界面上下文在Activity界面控制器类中被使用,当我们需要加载或者访问Activity相关的资源时,会需要用到该Activity的上下文对象。比如,我们需要在界面中创建一个控件,示例代码如清单2-10所示。
代码清单2-10
public class TestActivity extends Activity {
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView mTextView = new TextView(this);
label.setText("Test Text View");
setContentView(mTextView);
}
...
}
通过上面的代码片断,我们创建了一个文本框控件(TextView),并赋予该控件对应界面控制器(TestActivity)的上下文对象(this)。实际上,把界面控制器的上下文对象传递给控件,就意味着该控件拥有一个指向该界面对象的引用,可以引用界面对象占有的资源;同时,Android界面系统也将该控件绑定到该上下文指向的界面对象,最终组合并展示出来。
界面上下文(Activity Context)的生命周期跟Activity界面的是同步的,即当Activity被销毁的时候,其对应的上下文也被销毁了,同时,和该上下文有关的控件对象也将被销毁并回收。因此,我们也可以认为上下文可以用于串联Android应用之中的对象和组件,在理解了这点之后,在使用上下文的时候就不会迷惑了。此外,Context类中比较常用的方法如下。
getApplicationContext:获取当前应用的上下文对象,相关内容请参考2.5.2节。
getApplicationInfo:获取当前应用的完整信息并存于ApplicationInfo对象中,其中常用的信息包括包名packageName、图标icon以及权限permission等属性,更多属性可参考SDK中android.content.pm.ApplicationInfo类的说明。
getContentResolver:获取ContentResolver对象,用于查询所需的Content Provider提供的信息,更多知识请参考2.4.4节内容。
getPackageManager:获取PackageManager对象,PackageManager的用途比ApplicationInfo更加广泛,该类可以从系统的PackageManagerService中获取安装包和运行进程的信息,作用于系统范围。
getPackageName:获取包名,包名(packageName)可作为Android应用的唯一标识。
getResources:获取应用的资源对象Resources,该对象提供一系列的get方法来获取图形Drawable、字符串String以及视频Movie等资源。
getSharedPreferences:获取用于持久化存储的SharedPreferences对象,相关内容请参考2.6.1节。
getSystemService:获取系统级别服务的对象,Android应用框架为我们提供了丰富的系统服务,getSystemService方法就是用于获取这些系统服务对象并运用到应用开发中去。表2-4中列出了常用系统服务及其简单介绍,大家可以先了解一下。
表2-4 Android常用系统服务
服务名返回对象服务功能
ACTIVITY_SERVICEActivityManager系统应用程序管理
ALARM_SERVICEAlarmManager系统闹钟服务
CONNECTIVITY_SERVICEConnectivity网络连接服务
KEYGUARD_SERVICEKeyguardManager键盘锁服务
LAYOUT_INFLATER_SERVICELayoutInflater获取Xml模板中View组件服务
LOCATION_SERVICELocationManager位置服务,如GPS等
NOTIFICATION_SERVICENotificationManager状态栏和通知栏服务
POWER_SERVICEPowerManager系统电源管理
SEARCH_SERVICESearchManager系统搜索服务
TELEPHONY_SERVICETelephonyManager系统电话服务
VIBRATOR_SERVICEVibrator手机震动服务
WIFI_SERVICEWifiManager手机WIFI相关服务
WINDOW_SERVICEWindowManager系统窗口管理
界面上下文是Android应用开发中最经常被使用的上下文对象,应用界面中几乎所有的UI控件都需要用到,这一点在实际运用的过程中大家会体会得更深刻。
2.5.2应用上下文(Application Context)
应用上下文(Application Context)在整个应用(Application)开始的时候被创建,用于保存对整个应用资源的引用,在程序中可以通过界面上下文的getApplicationContext方法或者getApplication方法来获取。在实际应用的时候,我们通常会把应用上下文当做全局对象的引用来使用。当然,对于不同的应用我们会定义应用对象来使用,如代码清单2-11所示。
代码清单2-11
class TestApp extends Application {
...
private String status;
public String getStatus(){
return status;
}
public void setStatus(String s){
status = s;
}
...
}
TestApp应用类继承自Application基类,定义了自己的状态变量和get/set方法,可在整个应用程序中进行设置和获取。当然,我们还需要在应用程序的配置文件AndroidManifest.xml中进行配置,如代码清单2-12所示。
代码清单2-12
<application android:name=".TestApp"
android:icon="@drawable/icon"
android:label="@string/app_name">
…
</application>
配置完毕之后,在应用程序的Activity界面中就可以使用getApplicationContext来获取该应用的上下文对象来完成所需功能了,使用范例请参考代码清单2-13。
代码清单2-13
class TestActivity extends Activity {
...
@Override
public void onCreate(Bundle b){
...
TestApp app = (TestApp) this.getApplicationContext();
String status = app.getStatus();
...
}
...
}
实际上,在Android应用框架中,android.app.Activity类和android.app.Application类都是从android.content.Context类继承而来的,这也是为什么可以在Activity和Application中方便地使用this来代替对应上下文的原因。当然,理解两种Android上下文的用法在Android应用编程中是非常重要的,因为只有理解了Android上下文才能比较完整地理解Android应用的运行环境,进而更好地控制应用的运行状态。另外,我们也会在第7章中通过实例来加深大家对Android上下文用法的理解。
2.6Android数据存储
前面刚介绍过上下文对象的使用,其最重要的功能之一,就是用于存储应用运行期间产生的中间数据。接下来,我们来讨论Android应用中持久化类型数据的存储方案。对于移动互联网应用来说,我们经常把核心数据存储在服务端,也就是我们常说的“云端”,但是在实际项目中也会经常使用到Android系统内部的数据存储方案,接下来让我们认识一下几种最常用的数据存储方案。
2.6.1应用配置(Shared Preferences)
在Android系统中,系统配置(Shared Preferences)是一种轻量级的数据存储策略,只能用于存储key-value格式的数据(类似于ini格式),因此这个特点也决定了我们不可能在其中存储其他各种复杂格式的数据。由于系统配置使用起来比较简单方便,所以我们经常用它来存储一些类似于应用配置形式的信息。代码清单2-14就是一个简单的例子。
代码清单2-14
...
settings = getPreferences(Context.MODE_PRIVATE);
if (settings.getString("username", null) == null) {
SharedPreferences.Editor editor = settings.edit();
editor.putString("username", "james");
editor.commit();
}
...
以上代码的逻辑很简单:先检查是否存在“username”的值,若不存在则保存“james”字符串为“username”。这里我们重点分析两点:首先是关于Context.MODE_PRIVATE,MODE_PRIVATE代表此时Shared Preferences存储的数据是仅供应用内部访问的,除此之外,Android系统中还提供MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE两种模式,分别用于表示数据是否允许其他应用来读或者写;另外还需要注意的一点是,我们在操作数据的时候必须使用SharedPreferences.Editor接口来编辑和保存数据,最后还必须调用commit方法进行提交,否则数据将不会被保存。
另外,系统配置信息会被存储在“/data/data”下对应的应用包名下的shared_prefs目录里,一般是以XML文件格式来存储的。在Eclipse中,我们可以使用DDMS工具(本章的2.10.3节会介绍)打开对应的目录进行查看。
2.6.2 本地文件(Files)
将数据保存成为文件应该是所有系统都会提供的一种比较简单的数据保存方法,我们已经知道Android系统是基于Linux系统来开发的,而Linux系统就是一个文件系统,很多的数据都是以文件形式存在的。与系统配置不同,文件可存储的格式是没有限制的,所以使用范围自然也比系统配置广得多,除了可用于各种类型文件的读写,我们还经常用于保存一些二进制的缓存数据,比如图片等。
在Android中,我们一般使用openFileOutput方法来打开一个文件,此方法会返回一个FileInputStream对象,然后我们就可以选择使用合适的方法来操作数据。比如,对于cfg或者ini类型的文件来说,我们可以使用Properties的load方法来直接载入;对于其他普通的文件,我们则可以使用InputStreamReader和BufferedReader来读取。代码清单2-15就是一个典型的在Android系统中读取文件内容的例子。
代码清单2-15
...
public String getFileContent (String filePath) {
StringBuffer sb = new StringBuffer();
FileInputStream stream = null;
try {
stream = this.openFileInput(filePath);
BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (...) {
...
} finally {
if (stream != null) {
try {
stream.close();
} catch (...) {
...
}
}
}
return sb.toString();
}
...
在上面的代码中,我们实现了一个名为getFileContent的方法,用于获取对应文件的内容;其中就使用了openFileInput来获取文件数据,并通过一系列的拼装,最终返回整个文件的内容。另外,我们需要了解一下,在Android系统中,文件一般会存储到和配置文件同级的目录下,只不过目录名不是shared_prefs,而是files。更多关于Android文件存储的例子我们会在本书第7章中进行详细介绍。
2.6.3数据库(SQLite)
关于数据库的概念,我相信大家都已经非常熟悉了。Android系统给我们提供了一个强大的文本数据库,即SQLite数据库。它提供了与市面上的主流数据库(如MySQL、SQLServer等)类似的几乎所有的功能,包括事务(Transaction)。由于篇幅限制,我们不能在这里介绍太多关于SQLite数据库的内容,因此,如果大家想了解更多信息请到SQLite的官方网站(http://www.sqlite.org)查看。
与之前介绍的两种数据存储模式不同,数据库的存储方式偏向于存取的细节,比如,我们可以把同一类型的数据字段定义好,并保存到统一的数据表中去,进而可以针对每个数据进行更细节的处理。所以,如果可能的话,尽量使用数据库来存储数据,这样会大大增强应用的结构性和扩展性。另外,我们还经常把SQLite数据库和前面所提到的Android四大组件之一的“数据提供者”结合使用,因为它们对于“增删查改”接口的定义和使用实际上是一致的。另外,我们在使用的过程中经常通过继承SQLiteOpenHelper类并实现其中的抽象方法的形式来构造基础的DB操作类,使用范例如代码清单2-16所示。
代码清单2-16
...
public class DBHelper extends SQLiteOpenHelper {
/* 数据库配置 */
private static final int DB_VERSION = 1;
private static final String DB_NAME = "mydb.db";
private static final String DB_TABLE = "mytable";
/* 数据库初始化和更新SQL */
private static final String SQL_CREATE = "CREATE TABLE ...";
private static final String SQL_DELETE = "DROP TABLE ...";
/* 构造函数 */
public DBHelper(Context context){
super(context, DB_NAME, null, DB_VERSION);
}
/* 初始化数据库 */
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE);
}
/* 升级数据库 */
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DELETE);
}
}
...
此外,在需要使用的时候,我们可以通过getReadableDatabase和getWritableDatabase来获取数据库句柄分别进行读和写的操作。另外,数据库文件会被存在shared_prefs和files的同级目录下,目录名为databases。关于SQLite数据库的更多用法,我们也会在第7章中结合具体实例做进一步的介绍。
2.7Android应用界面
Android应用界面系统,即Android UI(User Interface)系统是Android应用框架最核心的内容之一,也是开发者们需要重点掌握的内容。如果我们把Android应用也分为前后端两部分的话,那么之前介绍的核心要点和四大组件等都属于后端,而Android UI系统则属于前端。后端保证应用的稳定运行,而前端则决定应用的外观和体验。对于一个优秀的Android应用来说,漂亮的外观和流畅的体验是必不可少的。接下来,我们便来学习Android外观系统的知识。
在2.3.3节中我们已经简单介绍了Android应用框架中的外观系统(View System),也就是Android UI系统的基础知识。我们知道了对于Android应用来说,最重要的两个基础类就是View和ViewGroup:View是绝大部分UI组件的基础类,而ViewGroup则是所有Layout布局组件的基类。当然,ViewGroup也是View的子类。相关类库的树形结构如下。
java.lang.Object
- android.view.View
- android.view.ViewGroup
- android.widget.FrameLayout
- android.widget.LinearLayout
- android.widget.TableLayout
- android.widget.RelativeLayout
- android.widget.AbsoluteLayout
本节将重点介绍Android应用(非游戏)使用的UI系统。一般来说,我们都使用XML格式的模板文件来书写对应的UI界面,当然,这种做法也比较符合MVC的设计思想。另外,由于UI模板独立于逻辑之外,界面设计师们就可以更加专注于他们自己的事情。在模板文件中,每个UI控件都由对应的XML标签来表示,具体的控件标签见表2-3,大家可以回顾一下。
2.7.1控件属性
我们知道Android UI系统给我们提供了丰富多彩的控件,比如TextView、Button、TextView、EditText、ListView、CheckBox、RadioButton等,具体如表2-3所示。我们可以使用这些不同功能的控件来完成各种各样用户界面的需求。那么控件本身的属性应该如何设置呢?实际上,每个UI控件都有很多的属性可供我们选择,我们一般都是通过设置这些属性来设置UI控件的外观、位置等。代码清单2-17中就是使用XML来表示文本框控件(TextView)的示例,实际的显示效果是在整个UI界面的左上方打印一段文字“I am a TextView”。
代码清单2-17
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a TextView" />
Android UI控件使用android:layout_width和android:layout_height属性控制其宽度和高度,属性值为wrap_content表示元素的外观由内容大小决定,而fill_parent则表示元素大小由外层的元素决定。我们经常使用fill_parent来实现自适应的界面布局,因为最外层的元素必然就是手机屏幕。此外我们需要注意的是,这两个属性是每个UI控件必须指定的。
另外,Android UI控件的外观采用类似于CSS标准的“盒子模型”,也有margin和padding的概念,元素内边距使用android:padding来表示,外边距则采用android:layout_margin来控制。这两个属性也是我们最常使用的“利器”之一,用其可使整个界面各个控件之间的间隔更为合理、美观。Android UI控件“盒子模型”如图2-7所示,大家可以结合示意图理解一下。
最后,我们来学习一些基础的Android UI控件属性,这些属性在UI组件基础类View类中定义,具备很强的通用性,可被绝大部分的UI控件所使用,因此也被称作“通用属性”。对于我们来说,只有掌握了这些通用属性的用法,才能够更好地控制UI组件并运用它们组装出各种各样的UI界面。
android:id:每个UI控件的代表性id。我们经常在程序中使用findViewById方法来选取对应id的控件,然后再对该控件进行属性控制或者事件处理,用法和HTML元素标签属性中的id类似。
android:background:控件背景,可以是颜色值,也可以是图像或者Drawable资源等,如果值为@null,则表示透明背景。
android:layout_width:UI控件的宽度,常见属性有fill_parent、wrap_parent等。前面我们已经简单介绍过这个属性的用法,它是每个控件必须具备的属性之一。
android:layout_height:UI控件的高度,常见属性和用法和宽度一样,也是每个控件必须具备的属性之一。
android:layout_gravity:用于控制UI控件相对于其外层控件的位置,其属性值就代表其位置,如顶部(top)、底部(bottom)、左边(left)、右侧(right)、垂直居中(center_vertical)、水平居中(center_horizontal)、绝对居中(center)、垂直填满(fill_vertical)、水平填满(fill_horizontal)、完全填满(fill)等。另外,这些属性可以并列存在,我们常使用“”符号隔开,如“center_verticalcenter_horizontal”表示垂直水平居中。
android:layout_margin:UI控件的外边距,使用方式见图2-7 所示的“盒子模型”。
android:padding:UI控件的内边距,使用方式见图2-7 所示的“盒子模型”。
android:gravity:控件内部的元素相对于控件本身的位置,其属性值和使用方法与android:layout_gravity基本一致。
android:visibility:显示或隐藏控件,控件默认是显示状态的。
通用属性常用于操控UI控件的外观和位置,通常能对UI界面的构建起到很大的作用。当然,除了通用属性之外,不同的UI控件还会有各自专属的“控件属性”,这些属性我们将在后面讲到各种UI控件的概念和用法时详细介绍,具体内容可参考第7章中与界面控件相关的章节内容。
2.7.2布局(Layout)
Android UI系统中的布局文件其实和HTML有点类似,都是用XML标签所代表的各种UI控件组合或者嵌套而成的,只不过,Android模板文件的格式比HTML更严谨些,属性也更复杂些。在Android UI界面设计中,Layout布局控件就像“建筑师”一样,帮助我们把整个界面的框架布局搭建起来,并把每个控件都放到合适的位置上。我们最经常使用的布局有以下几种,我们来逐个介绍一下。
1. 基本布局(FrameLayout)
基本布局(FrameLayout)是所有Android 布局中最基本的,此布局实际上只能算是一个“容器”,里面所有的元素都不能被指定位置,默认会被堆放到此布局的左上角。此布局在普通的应用中用得不是很多,但是因为简单高效,所以在一些游戏应用中还是经常被用到。
2. 线性布局(LinearLayout)
线性布局(LinearLayout)是应用开发中最常用的布局之一,分为横向和纵向两种,由android:orientation属性来控制。当属性值为“horizontal”时表示横向的线性布局,常用于并排元素的界面;而“vertical”则表示纵向也就是垂直的线性布局,它的用处更广,普通应用中的大部分界面都是垂直排列的,比如列表界面、配置界面等。
线性布局的用法很简单,就拿垂直的线性布局来说,我们只要把所需的控件按照顺序放到布局标签中间就可以了,Android UI系统会自动按照从上到下的顺序展示出来。代码清单2-18就是一个简单的代码示例,其功能很简单,就是把一个TextView和Button垂直并排在这个线性布局中。大家在阅读示例代码的同时可以顺便复习一下UI控件属性的用法。
代码清单2-18
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView android:id="@+id/text_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a TextView" />
<Button android:id="@+id/button_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a Button" />
</LinearLayout>
3. 相对布局(RelativeLayout)
相对布局(RelativeLayout)也是最常使用的布局之一,由于其内部的所有元素都是按照相对位置来排列的,所以不需要嵌套其他的布局,它可以使UI模板布局更加地简洁和高效。该布局中的控件元素都是以“参照控件”为准来排布的,比如控件属性设置为“android:layout_toRightOf="@+id/referBox"”,则表示该控件位于id为referBox的参照控件的右边。以下是相对布局中其他常用属性的列表,供大家参考。
android:layout_toLeftOf:该控件位于参照控件的左方。
android:layout_toRightOf:该控件位于参照控件的右方。
android:layout_above:该控件位于参照控件的上方。
android:layout_below:该控件位于参照控件的下方。
android:layout_alignParentLeft:该控件是否与父组件的左端对齐。
android:layout_alignParentRight:该控件是否与父组件的右端对齐。
android:layout_alignParentTop:该控件是否与父组件的顶部对齐。
android:layout_alignParentBottom:该控件是否与父组件的底部对齐。
android:layout_centerInParent:该控件是否与父组件居中对齐。
android:layout_centerHorizontal:该控件是否与父组件横向居中对齐。
android:layout_centerVertical:该控件是否与父组件垂直居中对齐。
4. 绝对布局(AbsoluteLayout)
绝对布局(AbsoluteLayout)的用法类似于HTML中的层属性“position=absolute”,该布局内部的控件可以使用android:layout_x和android:layout_y两个属性来指定它相对于布局坐标轴原点的X轴和Y轴方向的距离。图2-8就是绝对布局的示意图。
图2-8绝对布局的示意图
5. 表格布局(TableLayout)
大家如果熟悉HTML的话,应该非常熟悉表格布局(TableLayout),像一些表格型的信息列表都是使用表格布局来展示的。表格型布局的标签有两个—<TableLayout/>和<TableRow/>,前者是表格布局的主要标签,整个表格布局的框架,类似于HTML标签中的<table/>;后者是表格行,类似于HTML标签中的<th/>或者<tr/>。表格布局的使用范例如代码清单2-19所示。
代码清单2-19
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="0,1,2">
<TableRow>
<TextView android:gravity="center" android:text="ID"/>
<TextView android:gravity="center" android:text="NAME"/>
</TableRow>
<TableRow>
<TextView android:gravity="center" android:text="1"/>
<TextView android:gravity="center" android:text="james"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Edit"/>
</TableRow>
<TableRow>
<TextView android:gravity="center" android:text="2"/>
<TextView android:gravity="center" android:text="iris"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Edit"/>
</TableRow>
</TableLayout>
上述XML模板的最终显示结果如图2-9所示,我们可以看到,这是一个3行3列的标准表格结构,对应到代码中就是3个<TableRow/>标签,每个标签中包含3个控件。
图2-9表格布局示例
另外,我们还要注意的是,<TableLayout/>有3个很重要的属性:android:stretchColumns、android:shrinkColumns和android:collapseColumns,分别对应的是拉伸、收缩和隐藏列行为,如代码清单2-19中我们使用“android:stretchColumns="0,1,2"”,就表示所有列都是拉伸状态,因此每列中的控件才会平分并填满整行的空间;假如我们设置“android:collapseColumns="2"”,那么最右边的列将会被隐藏。
6. 标签布局(TabLayout)
标签布局(TabLayout)在移动应用中是相当流行的,其用法相对比其他布局复杂一些,需要配合程序来实现。接下来我们来看一个简单的标签布局的实例,其模板文件如代码清单2-20所示。
代码清单2-20
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<TabWidget android:id="@android:id/tabtitle_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<FrameLayout android:id="@android:id/tabcontent_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
</TabHost>
此界面最外面是一个<TabHost/>标签,里面嵌套了一个垂直的线性布局,该线形布局里面又包含了一个<TabWidget/>和<FrameLayout/>,这些标签都是需要在程序中设置的。紧接着,在模板对应的Activity类中设置该TabLayout的逻辑,使用范例见代码清单2-21。
代码清单2-21
public class TabDemo extends TabActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 初始化资源对象
Resources res = getResources();
TabHost tabHost = getTabHost();
TabHost.TabSpec ts;
Intent intent;
// 设置第1个Tab
intent = new Intent().setClass(this, Tab1Activity.class);
ts = tabHost
.newTabSpec("tab1")
.setIndicator("Tab1", res.getDrawable(R.drawable.ic_tab_1))
.setContent(intent);
tabHost.addTab(ts);
// 设置第2个Tab
intent = new Intent().setClass(this, Tab2Activity.class);
ts = tabHost
.newTabSpec("tab2")
.setIndicator("Tab2", res.getDrawable(R.drawable.ic_tab_2))
.setContent(intent);
tabHost.addTab(ts);
// 设置第3个Tab
intent = new Intent().setClass(this, Tab3Activity.class);
ts = tabHost
.newTabSpec("tab3")
.setIndicator("Tab3", res.getDrawable(R.drawable.ic_tab_3))
.setContent(intent);
tabHost.addTab(ts);
// 设置默认选中Tab
tabHost.setCurrentTab(0);
}
...
}
我们从代码注释中可以清楚地看到,在该实例中我们添加了3个Tab标签,程序使用newTabSpec方法获取TabHost.TabSpec对象,然后使用setIndicator和setContent方法设置Tab的顶部样式和内部信息,最后再调用setCurrentTab方法设置默认选中的标签页。最后,分别实现Tab1Activity、Tab2Activity和Tab3Activity界面类的逻辑,并加入声明到Manifest应用的配置文件中。至此,整个标签布局的设置就完成了。
2.7.3事件(Event)
了解完UI控件和界面布局的基本知识之后,我们还需要知道如何控制这些界面上的控件元素。Android应用框架为我们提供了事件机制来处理用户触发的动作,常见的事件包括键盘事件KeyEvent、输入事件InputEvent、触屏事件MotionEvent等。在实际应用中,我们需要掌握如何响应当用户操作这些控件时所触发的事件。比如,用户点击某个按钮控件(Button)之后需要执行一些程序逻辑,此时我们需要使用Android系统给我们提供的事件监听器Listener来捕获按钮的点击事件来执行这些逻辑。本节中我们将会介绍Android应用框架中比较常见的监听器。
1. View.OnClickListener事件
View.OnClickListener是最经常使用的监听器之一,用于处理点击事件。其实,该类也是View基类中的公用接口,其接口方法为onClick(View v)。方法只有一个参数,就是点击事件触发的控件对象的本身。我们在使用过程中必须实现onClick方法,也就是把点击之后需要处理的逻辑代码放到此方法中。代码清单2-22就是相关的使用范例。
代码清单2-22
btnObj = (Button) this.findViewById(R.id.demo_btn_id);
btnObj.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 按钮点击之后的动作
...
}
});
上面的代码实现的就是id为demo_btn_id的按钮控件的点击事件,我们在使用findViewById获取到按钮实例对象之后,又通过setOnClickListener方法设置View.OnClickListener监听器对象的实现,点击事件需要处理的逻辑我们会在onClick方法中实现。大家可以看到,Android UI事件的概念和用法与JavaScript语言有点类似。
2. View.OnFocusChangeListener事件
监听器View.OnFocusChangeListener用于处理选中事件,比如界面中有若干个UI控件,当需要根据选中不同的控件来处理不同的逻辑时,就可以使用按钮控件对象的setOnFocusChangeListener方法来设置View.OnClickListener监听器对象。选中需要处理的逻辑会在该监听器对象的onFocusChange方法中实现。
onFocusChange方法有两个参数:第一个是事件触发的控件对象,我们可以用其判断并处理不同控件的触发事件,另一个则是布尔型的值,表示改控件对象的最新状态。另外,监听器的具体用法和View.OnClickListener类似。
3. View.OnKeyListener事件
监听器View.OnKeyListener用于处理键盘的按键。我们可以在该监听器的onKey方法中处理用户点击不同按键时所需要处理的逻辑。在Android的键盘系统中,每个按键都有自己的代码,也就是keyCode。需要注意的是,onKey方法的第二个参数传递的就是用户点击按键的keyCode,而后我们就可以使用switch语句来处理不同的按键事件了。这个思路其实和JavaScript中的onkey系列方法非常类似,读者如果熟悉JavaScript的话,可以对照着学习一下。
4. View.OnTouchListener事件
监听器View.OnTouchListener用于处理Android系统的触屏事件。如果我们需要对一些触摸动作做处理,或者需要处理比点击动作此类动作更细粒度的动作的话,就要用到这个监听器了。此监听器必须实现的接口方法是onTouch(View v, MotionEvent event),我们需要注意的是第二个参数,因为这个参数表示的是用户触发的动作事件,我们可以根据这个参数的值来处理比较复杂的手势(gesture)动作。
MotionEvent事件中比较常见的动作和手势常量的说明如下,供大家参考。
ACTION_DOWN:按下手势,包含用户按下时的位置信息。
ACTION_UP:松开手势,包含用户离开时的位置信息。
ACTION_MOVE:拖动手势,包含最新的移动位置。
ACTION_CANCEL:结束手势,类似于ACTION_UP,但是不包含任何位置信息。
ACTION_OUTSIDE:离开控件元素时所触发的事件,只包含初始的位置信息。
EDGE_BOTTOM:碰触屏幕底部时所触发的事件。
EDGE_LEFT:碰触屏幕左边时所触发的事件。
EDGE_RIGHT:碰触屏幕右边时所触发的事件。
EDGE_TOP:碰触屏幕顶部时所触发的事件。
ACTION_MASK:多点触碰事件的标志,可用于处理多点触摸事件。
ACTION_POINTER_DOWN:第二点按下时的触发事件。
ACTION_POINTER_UP:第二点松开时的触发事件。
可以想象,如果缺少事件响应的支持,Android应用的界面将会变得毫无交互性。因此,学会使用UI控件的各种响应事件的用法对于Android应用开发来说是非常重要的。通常情况下,我们会使用不同的事件来让界面中的元素生动起来。比如,我们可以通过实现某个UI控件的View.OnClickListener事件来响应用户的点击动作(如代码清单2-22所示),或者还可以使用View.OnTouchListener事件来响应一些更加复杂的动作。
2.7.4菜单(Menu)
菜单是Android应用系统中最有特色的功能之一,也是每个Android应用必不可少的组件之一。合理地使用菜单不仅可以帮助我们节省界面空间,还可以提升用户的操作体验。一般,我们最常用的菜单有以下3种,下面我们分别来学习一下。
1. 选项菜单(Options Menu)
选项菜单(Options Menu)是Android应用中最经常被使用的菜单,当用户按下系统菜单键时出现。在Activity中,我们通常使用onCreateOptionsMenu方法来初始化菜单项,然后再使用onOptionsItemSelected方法处理每个菜单项选中时的逻辑。使用范例如代码清单2-23所示。
代码清单2-23
public class MenuActivity extends Activity {
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// 添加书写按钮菜单项
menu.add(0, MENU_APP_WRITE, 0, R.string.menu_app_write).setIcon(...);
// 添加注销按钮菜单项
menu.add(0, MENU_APP_LOGOUT, 0, R.string.menu_app_logout).setIcon(...);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_APP_WRITE:
// 点击书写菜单项之后的逻辑
...
break;
case MENU_APP_LOGOUT:
// 点击注销菜单项之后的逻辑
...
break;
}
return super.onOptionsItemSelected(item);
}
}
当然,如果我们需要添加一些每次菜单加载时都需要执行的逻辑,则需要使用onPrepareOptionsMenu方法来处理,因为onCreateOptionsMenu只在菜单项初始化的时候执行一次。
2. 上下文菜单(Context Menu)
上下文菜单(Context Menu)的概念和PC上应用软件的快捷菜单有点类似,在UI控件注册了此菜单对象以后,长按视图控件(2秒以上)就可以唤醒上下文菜单。在Activity类中,我们可以使用onCreateContextMenu方法来初始化上下文菜单。和选项菜单略微不同的是,此方法在每次菜单展示的时候都会被调用。另外,处理上下文菜单点击事件的方法名为onContextItemSelected,其用法和前面介绍的选项菜单中的onOptionsItemSelected方法类似。
3. 子菜单(Submenu)
在Android应用中点击子菜单时会弹出悬浮窗口显示子菜单项,子菜单(Submenu)可以被添加到其他的菜单中去。使用方法也很简单,Submenu的使用范例如代码清单2-24所示。我们需要注意的是,子菜单是不可以嵌套的,即子菜单中不能再包含其他子菜单,我们在使用的时候必须注意这个问题。
代码清单2-24
publicboolean onCreateOptionsMenu(Menu menu) {
// 初始化变量
int base = Menu.FIRST;
// 添加子菜单
SubMenu subMenu = menu.addSubMenu(base, base+1, Menu.NONE, "子菜单-1");
// 设置图标
subMenu.setIcon(R.drawable.settings);
// 添加子菜单项
subMenu.add(base, base+1, base+1, "子菜单项-1");
subMenu.add(base, base+2, base+2, "子菜单项-2");
subMenu.add(base, base+3, base+3, "子菜单项-3");
return true;
}
以上我们介绍了Android系统中最常见的几种菜单的概念和基本用法,关于菜单组件实际运用的更多信息,我们将在实战篇的7.5.1节中结合实际案例做进一步的介绍。
2.7.5主题(Theme)
为了让Android UI界面开发更加快速方便,同时具有更好的复用性,应用框架为我们提供了样式(style)和主题(theme)两个功能。这两个功能让我们可以更好地控制UI界面的外观,并可以实现一些更高级的功能,比如换肤功能等。
首先,需要了解的是,我们通常会把样式和主题的声明放在Android应用框架的资源目录res/values/下的styles.xml文件中,使用范例如代码清单2-25所示。
代码清单2-25
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CommonText" parent="@style/Text">
<item name="android:textSize">12px</item>
<item name="android:textColor">#008</item>
</style>
</resources>
我们可以看到,在这个样式文件中我们声明了一个名为“CommonText”的样式,里面包含了该样式的两个属性:字体大小“android:textSize”和字体颜色“android:textColor”属性。另外,样式是支持继承的,比如,该样式就继承自系统的基础“Text”样式,这种使用parent属性设置父样式的用法还是比较容易理解的。了解完样式和主题的写法,接下来让我们认识一下样式和主题之间的区别。
1. 样式(style)
Android的UI系统中,样式(style)的概念和CSS中样式的概念非常类似,我们可以把一些常用的样式提取出来,比如代码清单2-20中,我们就把一种常见的文字样式提取出来并保存为“CommonText”的样式。应用样式的时候,我们只需要在对应控件的声明中加上“style="@style/CommonText"”属性值即可。一般来说,样式都只会被应用于单个View控件中。
2. 主题(theme)
与样式不同,主题(theme)一般被用于更外层的ViewGroup控件中,比如,我们需要让Activity下所有控件的字体都用CommonText的样式,那么我们就可以在应用配置文件中的<activity/>标签加上“android:theme="CommonText"”的属性。但是,如果我们把样式用在ViewGroup上,对于ViewGroup之下的其他View控件却是没有影响的。另外,Android系统还定义了几个基本的系统主题供我们使用,比如Theme.Light主题就是以亮色背景为基调的主题样式。
学会灵活使用样式和主题来渲染Android应用的UI界面是非常重要的,因为该技术不仅可以让界面设计更加容易,还可以简化模板文件的代码,减少开发成本。因此,在实践的过程中,我们要有意识地去运用这些知识和技巧,逐渐掌握Android UI系统的使用。
2.7.6对话框(Dialog)
在Android应用界面中,经常需要弹出一些悬浮于底层UI界面之上的操作窗口。当这种窗口显示的时候,底层界面通常会被半透明层所覆盖住,焦点则会被该窗口获得,这种窗口就被称为对话框,或者是Dialog。应用中常用的Dialog有提示对话框(AlertDialog)、进度对话框(ProgressDialog)、日期选择对话框(DatePickerDialog)以及时间选择对话框(TimePickerDialog)等。在本节中,我们将重点介绍其中较常使用的两种Dialog的用法。
1. 提示对话框(AlertDialog)
提示对话框(AlertDialog)可以算是Android应用中最经常使用的对话框控件了,其主要用于显示提示信息,当然,可以加上确认和取消(YES和NO)按钮。创建AlertDialog需要使用AlertDialog.Builder子类,代码清单2-26演示了创建AlertDialog对话框的标准过程。
代码清单2-26
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to exit?")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
MyActivity.this.finish();
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
在以上代码中,首先使用AlertDialog.Builder(Context)方法来获取Builder对象,然后使用Builder类提供的公用方法来设置AlertDialog的文字和属性,接着使用该类的create方法来创建AlertDialog对象,最后调用show方法展示该对话框。显示效果如图2-10所示。
2. 进度对话框(ProgressDialog)
进度对话框(ProgressDialog)在Android应用开发中也经常会用到,主要用于在耗时操作等待时显示。其用法比较简单,一般情况下,只需要调用ProgressDialog的show方法即可,如代码清单2-27所示。
代码清单2-27
…
ProgressDialog dialog = ProgressDialog.show(this, "", "Loading. Please wait...", true);
…
以上代码创建了一个最基本的进度对话框,显示效果如图2-11所示。
当然,ProgressDialog类还提供了丰富的对话框属性设置方法,如设置进度条的样式、标题、提示信息,以及是否显示按钮等。更多用法示例可参考后面7.11.3节中的内容。至于其他对话框的用法由于篇幅原因,这里不做详细介绍。
2.8Android图形界面
前面介绍了Android应用界面(Android UI)的相关内容,不过对于一些游戏应用来说,这些UI控件往往派不上用场。此外,一些特殊的Android应用也有可能会使用到比较底层的图形类库,因此,本节我们就来学习Android的图形系统。
Android系统中的图形大致可以分为2D图形和3D图形两类,2D图形的类库在android.graphics包下,本节将会重点介绍;3D图形的类库在android.opengl包下,由于这部分内容和游戏开发关系比较紧密,这部分内容将被放在本书第13章中介绍,感兴趣的朋友可以提前参考13.1.4节中的内容。
2.8.1画笔(Paint)
首先,让我们来想象一下,当我们绘画的时候,最重要的两样东西是什么?答案应该没有什么悬念,那就是画笔和画布。实际上,在Android系统中绘制图形的原理是相同的,我们同样需要先使用程序构造一把画笔(Paint),然后在画布(Canvas)上进行绘画。
Android系统中的画笔类,即android.graphics包下的Paint类,该类包含了一系列的方法与属性,用于构造绘制图形用的画笔。我们把常用的方法归纳到表2-5中。
表2-5画笔类常用方法
方法名说明
setARGB(int a, int r, int g, int b)设置画笔透明度以及RGB颜色
setAlpha(int a)设置画笔透明度
setAntiAlias(boolean aa)设置抗锯齿效果
setColor(int color)设置画笔颜色
setLinearText(boolean linearText)设置线性文本
setPathEffect(PathEffect effect)设置路径效果
setShader(Shader shader)设置阴影效果
setStyle(Paint.Style style)设置画笔样式
setTextScaleX(float scaleX)设置文本缩放效果
setTextSize(float textSize)设置字体大小
以上方法常用于画笔初始化的配置逻辑中,接下来让我们来学习Paint画笔类的使用范例,如参考代码清单2-28所示。
代码清单2-28
public class TestPaintView extends View {
...
private Paint mPaint = new Paint();
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画笔
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setAlpha(200);
mPaint.setStyle(Paint.Style.FILL);
// 绘制矩形
canvas.drawRect(100, 100, 150, 150, mPaint);
}
...
}
以上视图类TestPaintView继承自View基类,主要的绘制逻辑在onDraw方法中,即使用定制好的实心画笔绘制一个红色的矩形,这里我们可以学习到使用Paint画笔类的正确方法。此外,我们还需要注意,这里在使用setColor方法设置画笔颜色的时候,用到了Color类的预定义颜色常量,我们将这些常用的颜色常量归纳到表2-6中。
表2-6画笔类颜色常量
常量名说明
Color.BLACK黑色
Color.BLUE蓝色
Color.CYAN青绿色
Color.DKGRAY灰黑色
Color.GRAY灰色
Color.GREEN绿色
Color.LTGRAY浅灰色
Color.MAGENTA红紫色
Color.RED红色
Color.TRANSPARENT透明
Color.WHITE白色
Color.YELLOW黄色
2.8.2画布(Canvas)
设置好画笔和颜色,就可以开始在画布上绘画了,这时我们就需要用到画布类,即Canvas类。该类包含了一系列的方法与属性,用于设置画布的外观,我们把常用的方法归纳到表2-7中。
Canvas类中常用绘制方法的用法比较简单,Android系统已经在View类的onDraw方法中默认传入了canvas对象,我们可以根据需要使用不同的draw方法绘制出不同的图形。比如,代码清单2-29中就使用了drawRect方法绘制了一个矩形。
表2-7画布类常用方法
方法名说明
clipRect(int left, int top, int right, int bottom)剪裁画布,即需要绘制的部分
drawARGB(int a, int r, int g, int b)设置整个画布的颜色
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)绘制位图
drawCircle(float cx, float cy, float radius, Paint paint)绘制圆形
drawColor(int color)设置画布背景色
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)绘制线形
drawOval(RectF oval, Paint paint)绘制椭圆
drawPoint(float x, float y, Paint paint)绘制点形
drawRect(float left, float top, float right, float bottom, Paint paint)绘制矩形
drawText(String text, float x, float y, Paint paint)绘制文字
restore()重置画布
rotate(float degrees)旋转画布
save()保存画布
然而,游戏应用的画布中通常不只有一个图形,通常需要对其中的某些图形进行特殊处理,比如旋转、变形等,此时需要先使用save方法来保存画布,图形处理完毕之后再调用restore方法来重置、重绘,使用范例如代码清单2-29所示。
代码清单2-29
public class TestCanvasView extends View {
...
private Paint mPaint = new Paint();
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画布颜色
canvas.drawColor(Color.BLACK);
// 设置画笔
mPaint.setAntiAlias(true);
// 剪裁画布
canvas.clipRect(0, 0, 200, 200);
// 保存画布
canvas.save();
// 绘制一个矩形
canvas.rotate(10.0f);
mPaint.setColor(Color.RED);
canvas.drawRect(100, 100, 150, 150, mPaint);
// 重置画布
canvas.restore();
// 绘制另一个矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(100, 0, 200, 100, mPaint);
}
...
}
以上程序绘制了两个矩形。其中,红色的矩形绕着屏幕左上方的顶点顺时间旋转了10°。这里涉及Canvas画布坐标系的知识,我们将在2.8.3节中介绍。另外,我们还可以学习到如何对Canvas画布进行设置、保存、旋转、重置等一系列的操控过程。学习了以上Paint和Canvas类的编程技巧之后,开发者就可以在Android应用和游戏中方便地绘图了。
2.8.3 基础几何图形
前面我们已经学习了画笔(Paint)和画布(Canvas)的基础知识,接下来我们就可以使用这些工具来画图了。实际上,在前面的代码范例中,我们已经介绍了如何使用Canvas对象的drawRect方法来绘制矩形,但是大家可能还不清楚方法中参数值的含义,因此我们先来熟悉Canvas画布的坐标系,如图2-12所示。
从以上的坐标系示意图中,我们可以看出以下几个要点。其一,Canvas画布的坐标原点位于整张画布的左上方,点坐标为“(0,0)”;其二,屏幕横向的是X轴,纵向的是Y轴,屏幕内的点坐标都是正数;其三,以矩形为例,我们可以看到绘图方法(drawRect)中的left、top、right、bottom等参数的含义,其他方法中的类似参数的含义都可以依此类推。
另外,在使用Canvas进行绘图的时候还要注意,画布是按照程序逻辑的先后顺序进行渲染的,因此底部图形的渲染逻辑放在前面,渲染逻辑在后面的图形则会层层覆盖上去,使用范例请参考代码清单2-30。
代码清单2-30
public class TestGraphicsView extends View {
...
private Paint mPaint = new Paint();
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画布颜色
canvas.drawColor(Color.BLACK);
// 设置画笔
mPaint.setAntiAlias(true);
// 画圆形
mPaint.setColor(Color.YELLOW);
canvas.drawCircle(160, 160, 120, mPaint);
// 画矩形
mPaint.setColor(Color.RED);
canvas.drawRect(80, 80, 240, 240, mPaint);
// 画椭圆
mPaint.setColor(Color.GREEN);
RectF rectf = new RectF();
rectf.left = 90;
rectf.top = 100;
rectf.right = 230;
rectf.bottom = 220;
canvas.drawOval(rectf, mPaint);
// 画多边形
Path path = new Path();
path.moveTo(160, 110);
path.lineTo(160-40, 110+80);
path.lineTo(160+40, 110+80);
path.close();
mPaint.setColor(Color.BLUE);
canvas.drawPath(path, mPaint);
...
}
...
}
在上述代码中,TestGraphicsView类的onDraw方法中依次绘制了圆形、矩形、椭圆和多边形,运行结果如图2-13所示,我们可以很清楚地看到这些基础几何图形的显示效果以及图形渲染的先后顺序。
基础几何图形的绘制是Android图形系统的基础知识。在此基础之上,我们可以把Android UI控件结合到一起,开发出丰富多彩的应用UI界面。当然,我们还可以运用View控件的刷新机制完成一些简单的图形动画,相关内容将在2.8.4节中介绍。
2.8.4常见图形变换
常见的图形变换包括位移、旋转、缩放、倾斜等,其中,位移变换在开发者掌握了画布坐标系等基础概念的情况下,实现起来是比较简单的;然而,旋转、缩放以及倾斜变换则涉及变换矩阵(Matrix)的概念,这里需要特别解释一下。
Android系统中的变换矩阵实际上是一个3×3的矩阵,专门用于控制图形变换,矩阵中的每个数值都有其特定的含义。Android SDK中的Matrix类位于android.graphics包下,我们可以通过setValue方法直接设置旋转矩阵的二维数组,但是这种用法比较难懂,更简单的用法是使用Matrix类提供的方法来控制旋转矩阵,比如setRotate方法就用于设定旋转的角度。代码清单2-31就展示了Matrix类的用法。
代码清单2-31
public class TestImageView extends View implements Runnable {
private Bitmap star = null;
private int starWidth = 0;
private int starHeight = 0;
private float starAngle = 0.0f;
private Matrix starMatrix = new Matrix();
public TestImageView(Context context) {
super(context);
// 加载资源
Resources res = this.getResources();
star = BitmapFactory.decodeResource(res, R.drawable.star);
// 获取原始图片宽高
starWidth = star.getWidth();
starHeight = star.getHeight();
// 开始重绘视图
new Thread(this).start();
}
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 重置旋转矩阵
starMatrix.reset();
// 设置旋转角度
starMatrix.setRotate(starAngle);
// 重绘旋转的图形
Bitmap starBitmap = Bitmap.createBitmap(star, 0, 0, starWidth, starHeight, starMatrix, true);
canvas.drawBitmap(starBitmap, 0, 0, null);
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(100);
starAngle++; // 旋转角度
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 通知主线程更新图像
this.postInvalidate();
}
}
}
上述代码中的TestImageView类是一个完整的重绘画布视图的例子。首先,该类继承自View基类,同时还包含了一个线程类的run方法,在该方法的逻辑中,每100ms进行一次重绘,即调用postInvalidate方法通知主线程更新图像。其次,在TestImageView类的构造方法中,主要包含了资源初始化的逻辑,这里程序加载了一个五星形状的图像资源文件。另外,在onDraw方法中,我们可以看到starMatrix变换矩阵的常见用法之一,即通过setRotate方法设置旋转的角度。该程序最终的运行效果,就是画出了一个绕着屏幕左上方顺时针旋转的五角星,如图2-14所示。
当然,我们还可以让图像绕着某个中心点旋转,这也不是问题,我们只需要对onDraw方法的逻辑稍做修改即可,修改过的逻辑实现如代码清单2-32所示。
代码清单2-32
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 重置旋转矩阵
starMatrix.reset();
// 设置旋转中心
float transX = 100;
float transY = 100;
float pivotX = starWidth/2;
float pivotY = starHeight/2;
starMatrix.setRotate(starAngle, pivotX, pivotY);
starMatrix.postTranslate(transX, transY);
// 重绘旋转的图形
canvas.drawBitmap(star, starMatrix, null);
}
...
要让图形绕着其中心旋转,首先要使用setRotate方法设置图形的旋转中心,然后再使用postTranslate方法把图形平移到相应的位置,即坐标(transX,transY)。该实例的运行效果如图2-15所示,我们可以看到屏幕上出现了一个不断自转的五角星。
当然,除了旋转之外,常见的图形变换还包括大小变换、倾斜变换等,限于篇幅,这里就不做介绍了,有兴趣的读者可以参考Matrix类文档中的preScale、postScale、preSkew、postSkew等方法。这里我们还需要注意的是pre和post系列方法的区别,带有pre前缀的方法表示此变换逻辑需要应用在所有变换逻辑之前,而带有post前缀的方法则表示此变换逻辑会依次往后排列,因此代码清单2-28中的旋转逻辑也可以使用代码清单2-33中的代码替代。
代码清单2-33
...
public void onDraw(Canvas canvas) {
...
starMatrix.setTranslate(transX, transY);
starMatrix.preRotate(starAngle, pivotX, pivotY);
...
}
...
2.9Android动画效果
适当地使用动画效果可以很好地提升Android应用或游戏的操作体验。目前Android系统支持的动画效果主要有两种,即逐帧动画(Frame Animation)和补间动画(Tween Animation)。虽然,在Android 3.0以后的版本中还引入了新的动画系统,但是目前最主流的动画效果还是这两种。
2.9.1逐帧动画(Frame Animation)
逐帧动画类似于GIF动画图片,即按照顺序播放图片。我们通常会在Android项目的res/drawable/目录下面定义逐帧动画的XML模板文件。编码的时候,需要在动画模板文件的<animation-list/>标签中依次放入需要播放的图片,并设置好播放的间隔时间,如代码清单2-34所示。
代码清单2-34
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/a001" android:duration="100"/>
<item android:drawable="@drawable/a002" android:duration="100"/>
<item android:drawable="@drawable/a003" android:duration="100"/>
...
</animation-list>
然后,就可以在Activity界面控制器的逻辑中自由使用了。需要注意的是,逐帧动画并不能独立使用,动画效果的显示还是要借助于ImageView图像控件,简单地说,也就是把动画效果绑定到对应的ImageView图片对象上。假设这里的ImageView元素的ID值,即android:id属性值为img_frame_anim,而之前定义的动画模板文件名为demo_frame_anim.xml,逐帧动画的使用范例如代码清单2-35所示。
代码清单2-35
public class DemoAnimationActivity extends Activity {
ImageView iv;
AnimationDrawable ad;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取对应图片的ImageView对象
iv = (ImageView) findViewById(R.id.img_frame_anim);
// 设置对应图片的背景为动画模板文件
iv.setBackgroundResource(R.drawable.demo_frame_anim);
// 初始化动画对象
ad = (AnimationDrawable) imageView.getBackground();
// 开始动画
ad.start();
}
public void onPause() {
super.onPause();
// 停止动画
ad.stop();
}
...
}
以上代码的逻辑非常简单,我们可以重点关注AnimationDrawable对象的用法,即如何使用start和stop方法控制逐帧动画的播放和停止。
2.9.2补间动画(Tween Animation)
补间动画与逐帧动画在本质上是不同的,逐帧动画通过连续播放图片来模拟动画的效果,而补间动画则是通过在两个关键帧之间补充渐变的动画效果来实现的。目前Android应用框架支持的补间动画效果有以下5种。具体实现在android.view.animation类库中。
AlphaAnimation:透明度(alpha)渐变效果,对应<alpha/>标签。
TranslateAnimation:位移渐变,需要指定移动点的开始和结束坐标,对应<translate/>标签。
ScaleAnimation:缩放渐变,可以指定缩放的参考点,对应<scale/>标签。
RotateAnimation:旋转渐变,可以指定旋转的参考点,对应<rotate/>标签。
AnimationSet:组合渐变,支持组合多种渐变效果,对应<set/>标签。
补间动画的效果同样可以使用XML语言来定义,这些动画模板文件通常会被放在Android项目的res/anim/目录下。比如,代码清单2-36中就定义了一个组合式的渐变动画效果。
代码清单2-36
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="1000" />
<scale
android:fromXScale="0.1"
android:toXScale="1.0"
android:fromYScale="0.1"
android:toYScale="1.0"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="100" />
</set>
以上补间动画有两个效果:首先,在1秒(1000ms)的时间内,透明度从0(完全透明)变成1(不透明);同时,大小从原先的1/10变成正常大小,缩放的中心点是元素的中心位置。假设以上动画效果的模板文件名为demo_tween_anim.xml,现在我们要把该动画效果应用到一张ID为img_tween_anim的图片上,实现方法见代码清单2-37。
代码清单2-37
...
ImageView iv = (ImageView) findViewById(R.id.img_tween_anim);
Animation anim = AnimationUtils.loadAnimation(this, R.anim.demo_tween_anim);
iv.startAnimation(anim);
...
在实际项目中,我们经常使用补间动画,原因是补间动画使用起来比较方便,功能也比逐帧动画强大不少,而且还可以很方便地进行动画叠加,实现更加复杂的效果。实际上,代码清单2-36中的<set/>标签对应的就是AnimationSet类,即“动画集合”的概念,支持加入多种动画效果,如渐变动画(alpha)、大小动画(scale),线性动画(translate)等。另外,在Android系统中,所有与动画相关的类都归类在android.view.animation包之下,大家可以参考SDK文档进行进一步学习。
至此,我们已经初步了解了如何在Android系统中使用各种动画效果,包括逐帧动画和补间动画。显而易见的是,在Android平台之上,开发者们可以很方便地使用各种动画效果来为应用产品增色。此外,使用动画效果还可以帮助我们制作出简单的Android游戏,更多与Android游戏开发有关的内容请参考本书第13章。
2.10Android开发环境
前面我们已经学习了Android系统中最重要的基础概念的内容,那么接下来就要开始正式进入Android应用的实战开发阶段。“工欲善其事,必先利其器”,因此,我们先来熟悉Android应用的开发环境吧。
Android应用的开发环境是基于Eclipse平台的,Eclipse的强大无需多说,它当然也适应于Windows XP、Mac OS、Linux等多种操作系统。另外,我们还需要安装一些必备的开发工具包,所需要的软件见表2-8。
表2-8Android应用的开发环境必备的开发工具
软件版本下载地址
Android SDKAndroid SDK 2.2http://developer.android.com/sdk/index.html
Java SDKJDK 1.6http://java.sun.com
EclipseClassic版本http://www.eclipse.org
ADT最新版本https://dl-ssl.google.com/android/eclipse/
2.10.1开发环境的搭建
在搭建开发环境之前,我们先来介绍一下Android开发环境的几个重要组成部分以及它们的安装方式。
1. Android SDK
Android SDK的安装非常简单。首先,直接打开前面提到的Android SDK的下载地址,下载最新的android-sdk_r13-windows.zip安装包;下载完毕之后,在电脑上解压,你会发现android-sdk-windows这个目录,这个目录就是Android SDK目录了,你可以把它复制到你所希望的位置并重新命名,比如D:\Android;然后,打开SDK Manager.exe,你会看到如图2-16所示的安装界面。接下来,单击“OK”按钮让下载过程继续就可以了。系统会自动下载最新的Android SDK及其文档和例子等。当然,这个过程是很漫长的,如果有可能,建议从已经下载过的朋友那里复制一份。
图2-16 Android SDK安装界面
2. Java SDK
Java SDK的安装过程也是很简单的,不过下载地址可能有点难找,如果找不到请尝试从以下地址下载:http://www.oracle.com/technetwork/java/javase/downloads/index.html。下载完最新版的JDK版本之后,使用软件自动安装即可。要注意的是,在安装完毕之后需要设置Windows系统的环境变量,如图2-17所示。
图2-17Java SDK安装界面
设置完毕之后,我们可以在Windows命令行中使用“java -version”命令行来检测JDK是否安装成功。如果运行结果如图2-18所示,则表示安装成功。
图2-18检测JDK是否安装成功
3. Eclipse
Eclipse开发工具的安装也是非常简单的,进入http://www.eclipse.org/downloads/页面,下载Eclipse Classic最新版本的ZIP压缩包,解压缩后再复制到相应目录,比如D:\Eclipse。打开eclipse.exe就可以看到以下界面,如图2-19所示。
图2-19Eclipse界面
由于前面已经安装过Java SDK,所以直接打开eclipse.exe就会看到以上界面,否则打开时会提示错误。下面为没有使用过Eclipse的朋友大致介绍一下Eclipse的操作界面:最上面的那一排文字是“选项菜单栏”,包括几乎Eclipse中所有的操作;“选项菜单栏”的下面那排是常用项目的“快捷图标栏”;左边是Package Explorer,即“项目文件浏览框”,主要用于管理项目代码;中间是“代码编辑框”,我们在这里编辑代码;右边是Outline“代码大纲框”,这里可以方便地进行代码概览;右下方则是“调试信息框”,这里面包括Problems错误提示框、Console调试信息结果框等。
4. ADT
实际上,ADT(Android Development Tools)是Eclipse开发工具的一个插件,其安装过程也很简单:首先单击Eclipse界面上方的“Help”菜单,然后选择“Install New Software ”命令,接着在“Work with”输入框输入ADT插件地址“https://dl-ssl.google.com/android/eclipse”,单击“Add”按钮添加插件站点即可。当下方窗口出现选项列表时,单击选择所有的安装选项,然后按照提示安装即可,如图2-20所示。
安装完成后,会在左上方的“快捷图标栏”中出现ADT的快捷图标,即。单击此图标,系统会自动打开“Android SDK and AVD Manager”(Android虚拟设备管理器)界面,如图2-21所示。在这里我们可以创建并管理我们所需要的虚拟设备。此时,右边的“虚拟设配列表”中是空的。
在真正地开始创建设备之前,我们还需要配置一下ADT中Android的SDK位置,配置过程如下:执行“Window”菜单中的“Preferences”命令,然后选择左边的“Android”选项,然后在右边的“SDK Location”中选择Android SDK安装的位置。比如,之前我们把Android SDK安装到D:\Android目录下,那么我们在这里就选择该目录,如图2-22所示。
图2-20ADT插件安装界面
图2-21Android虚拟设备管理器
图2-22配置ADT
Android虚拟设备管理器是用来运行和调试我们所开发的Android应用程序的,它可以模拟各个版本几乎所有的Android设备。如果你要添加一个新设备,就单击右边的“New”按钮,并按照图2-23配置所需要的设备,最后单击“Create AVD”完成创建,结果会在“虚拟设配列表”中显示,如图2-24所示。
图2-23创建虚拟设备
这里简单介绍一下Android虚拟设备的主要配置选项。
Name:虚拟设备的名称。
Target:设备的Android API版本,考虑到兼容性,这里选择Android 2.2的API。
SD Card:设备的存盘大小。
Skin:设备外观,我们可以选择主流的设备,也可以直接指定设备的宽度和高度。
Hardware:设备硬件,如果你的设备需要有一些特殊的硬件,可以在这里进行配置。当然,还可以使用右侧的“New”按钮来添加所需要的虚拟硬件设备。
图2-24虚拟设备创建成功
图2-24所示的就是创建完毕的AVD的虚拟设备列表界面,大家可以看到这次在右侧的设备列表中已经多出一个名为“Android_2.2”的AVD虚拟设备,我们可以选中它,然后单击右边的“Start”按钮,然后等待一段时间,即可看到虚拟设备界面,效果还是很不错的,如图2-25所示。
图2-25成功运行虚拟设备
当然,如果你觉得虚拟设备的速度太慢,我们也可以使用真机来调试。其实,操作起来也很简单,安装步骤如下。
步骤1:安装手机的驱动,保证手机在Windows XP上可以被识别。
步骤2:打开手机的“设置”,然后选择“应用程序”中的开发选项,打开“USB调试”和“允许模拟地点”选项。
步骤3:打开Eclipse中的DDMS,在左边的Devices列表中就可以看到你的真机设备,单击选中它,就可以开始在真机上进行安装和调试了。
之后,我们就可以通过USB连接线把手机设备与开发机器连接起来,直接把Android应用程序安装到手机设备上进行调试。实际上,真机调试是正规Android应用程序发布的必要步骤,因为Android的手机设备型号非常多,所以在上线之前应尽量多测一些手机设备,保证Android应用的兼容性。
2.10.2首个Android项目
前面我们已经把Android的开发环境准备好了,下面我们将使用Eclipse+ADT来创建自己的首个Android项目,也就是我们常说的Hello World项目,具体步骤如下。
步骤1:打开Eclipse开发工具,单击左上方的“新建项目”菜单创建一个项目,然后选择“Android Project”子项,单击“Next”按钮,如图2-26所示。
步骤2:在接下来的新建项目界面中的“Project Name”(项目名)文本框中填入项目的名字hello;在“Build Target”选项组中选择“Android 2.2”,这里的选项应该和前面建立AVD时采用的Android版本保持一致;在“Package name”文本框中填写包名“com.app.hello”;在“Create Activity”文本框中填入需要建立的默认Activity类名HelloActivity,如图2-27所示。
小贴士:以下的“hello”项目同“Hello World”项目。
图2-26创建Android项目
图2-27项目创建界面
步骤3:单击“Finish”按钮,ADT会自动生成代码并把项目建好。完成之后,我们就可以在Eclipse界面左边的“Package Explorer”窗口中看到创建完毕的名为“hello”的项目了。接下来,我们试着发布并运行此项目。右键单击hello项目,在快捷菜单中执行“Run As”→“Android Application”命令,如图2-28所示。
图2-28运行hello项目
步骤4:Eclipse会帮助我们自动完成代码编译工作,并安装到Android模拟器上运行,hello项目的最终运行效果如图2-29所示。
图2-29hello项目运行效果
至此,我们已经成功建立了自己的首个Hello World项目,虽然没有写过一行代码,这就是使用ADT环境给我们带来的好处。接下来,我们就以Hello World项目为例,分析一下Android应用的几个主要组成部分。首先,我们来看一下应用的基础配置文件,也就是AndroidManifest.xml文件,见代码清单2-38。
代码清单2-38
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.hello"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".HelloActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
可以看到,项目应用配置文件AndroidManifest.xml中不仅声明了Hello World应用的package名、版本号android:version、最小的sdk版本限制android:minSdkVersion等,还在<application>元素里面声明了应用中唯一的Activity,也就是HelloActivity的配置信息。此外,在hello项目的配置文件中,我们还需要注意以下几点。
package名就是应用安装时用的类包名,务必保证该名不和其他应用重名,否则安装时会发生严重冲突。
<activity>元素中的android:name必须和相关的Activity类对应上,比如“.HelloActivity”应该和“com.app.hello.HelloActivity”类对应。
<intent-filter>元素用于决定activity的调用方式,比如“android.intent.action.MAIN”就说明这个Activity是该应用的总入口,一个应用有且只能有一个MAIN入口Activity。
由于Hello World项目比较简单,大家在这里只能看到项目应用配置文件很小一部分的用法,比如Android应用的基础声明、Activity组件的简单配置等。关于配置文件其他更高级的用法,比如除Activity之外的其他重要组件的配置方法,以及关于消息过滤器<intent-filter>的完整用法等,我们都将在实战篇中的7.1.1节中详细介绍。
接下来,打开源码目录src/下的com.app.hello代码包中的主要界面程序的Java程序HelloActivity.java的代码,如代码清单2-39所示。该类的代码逻辑比较简单,HelloActivity类继承了Activity基类;接着,在此类的onCreate接口中使用了setContentView方法设置本Activity所使用的layout模板;最后,在运行的时候,系统就会展示出对应的UI界面。
代码清单2-39
package com.app.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
读到这里,也许大家会有疑问,虽然我们设置了R.layout.main模板,但是模板文件在哪里呢?要解决这个问题,我们需要熟悉一下Android的基本目录结构,我们就以Hello World项目为例,项目的目录结构如图2-30所示。
对照图2-30,我们来讲解一下Android项目中常见的目录结构。
hello/src/com.app.hello:程序包目录,这里根据前面新建项目的时候填写的“Package Name”选项来生成应用程序包的Namespace命名空间。
hello/gen/com.app.hello:存储ADT自动生成的资源映射文件R.java,此文件内的静态类分别对应着Android不同的资源类型,而类中的静态类变量就表示对应资源的id标识,比如,R.layout.main对应hello/res/layout/main.xml布局文件。
hello/res/drawable-hdpi:图片以及渲染文件存储目录,在Android 2.1之后,原先的drawable目录被扩充为3个目录drawable-hdpi、drawable-ldpi和drawable-mdpi,主要是为了支持多分辨率,drawable-hdpi存放高分辨率图片,如WVGA(480×800),FWVGA(480×540)。
hello/res/drawable-ldpi:中等分辨率图片存储目录,如HVGA(320×480)。
hello/res/drawable-mdpi:低分辨率图片存储目录,如QVGA(240×320)。
hello/res/layout:布局文件存储目录,这里就是存储layout模板的地方了。
hello/res/values:配置文件存储目录,如strings.xml、colors.xml等。如果你要开发Android的国际化程序,可以在这里为不同的地区所支持的语言设置不同的目录,比如中文简体hello/res/values-zh-rCN,而中文繁体则为hello/res/values-zh-rTW。
hello/AndroidManifest.xml:每个Android项目都必需的基础配置文件,除了声明程序中的Activities、ContentProviders、Services和Intent Receivers,还可以指定permissions和instrumentations(安全控制和测试)等。
hello/proguard.cfg:主要用于Android应用代码的安全混淆。
理解以上内容之后,可以尝试着动手给Hello World项目的代码做一些小修改,比如,调整一下打印出来的文字,或者修改一下布局的方式等。这样不仅可以加深对Android项目开发的印象,还可以帮助大家快速地熟悉Android应用的开发工具,为后面的项目实践做准备。
2.10.3使用DDMS调试工具
在完成了首个Hello World项目的创建之后,大家应该可以体会到在Eclipse加上ADT的开发环境中进行Android代码开发是一件多么方便的事情。而实际上,ADT还给我们提供了一个非常方便的调试工具,那就是DDMS。使用这个工具,代码调试工作也变得简单起来。我们只需要单击Eclipse界面右上方的DDMS按钮就可以切换到DDMS界面了,如图2-31所示。
图2-31DDMS调试界面
接下来,我们按照“从左到右,从上到下”的顺序介绍一下该工具中的几个主要功能板块的功能和使用。
Devices:该窗口用于显示所有设备的详细信息,这里的emulator-5554就是模拟器设备的编号,下面则是设备运行的所有进程的列表,单击相应的进程还可以进行调试、截屏等动作。
Emulator Control:这里主要用于操控一些模拟器的行为,比如设置GPS定位信息等。
File Explorer:本窗口是Android系统的文件浏览器,在这里,我们可以浏览设备里面的文件目录,比如,之前在讲Android数据存储的时候提到过可以使用DDMS来浏览对应的存储文件,讲的就是这个窗口的功能。
LogCat:用于打印设备的调试信息,这个窗口应该是在开发过程中最经常用到的了,这里的信息分为五级,分别对应上面的V(VERBOSE)、D(DEBUG)、I(INFO)、W(WARN)、E(ERROR)五个圆形的按钮。此外,还可以通过单击这些按钮来过滤相应的调试信息。
Console:控制台打印的主要是操作信息,在这里,可以查看设备的运行情况,比如应用的apk包是否安装成功等。
在这些功能板块中,我们重点介绍一下LogCat窗口的使用,因为开发的时候最经常使用到的就是它了。在Android程序中,我们可以使用android.util.Log类里面的方法来打印不同级别的信息,笔者个人在调试的时候比较喜欢使用WARN级别,因为INFO以上的信息太多了,不利于过滤,而ERROR又太严重,经常和一些Exception混起来。另外,笔者个人还非常喜欢直接把它拉到开发界面中去,这样不需要切换到DDMS就可以调试程序了。以上是笔者本人的一些使用心得,如果你觉得不错的话不妨试一试。
当然,DDMS的用法不只有上面提到的这些功能,关于DDMS的使用心得,大家应该在Android应用的开发和调试中注意积累。另外,本书实战篇中的7.1.3节也会结合实际应用进一步说明DDMS工具的用法。总之,学会如何灵活地使用DDMS来调试Android应用程序是Android应用开发中必不可少的知识和技巧。
2.11小结
在本章中,首先我们学习了Android的系统框架和应用框架,然后熟悉了Android的四大核心要点和四大组件(活动Activity、服务Service、广播接收器Broadcast Receiver、内容提供者Content Provider),以及Android中一些常用的数据存储方式。随后,我们还学会了如何安装和配置Android的开发环境,并且动手开发了第一个Android应用Hello World项目,还学习了一些使用DDMS进行调试的方法。
最后,建议大家回顾一下本章的所有知识点,如果感觉都已经理解掌握了的话,那么要恭喜,你已经成功迈出成为Android大师的第一步;当然,如果你感觉思路还有点不够清晰的话,请回头好好回顾并理解一下本章的内容,因为这些知识对你以后继续深入地学习Android系统是非常重要的。
第3章PHP开发准备
通过本章,读者将快速地学会如何使用PHP语言进行服务端开发。当然,如果你之前已经有过一些服务端开发的基础,学习本章内容将会更加轻松;然而,如果你以前一直专注于客户端开发,则更需要仔细阅读本章的内容,因为本章将通过讲解PHP语言引领你进入服务端开发的世界,并理解一些服务端开发通用的方法和思路。
本章首先会给大家介绍PHP的开发基础,以及一些面向对象编程的技巧;然后紧接着给大家介绍PHP开发环境(Xampp)的搭建和一些其他主要的与之配套的服务端组件(Apache和MySQL)的基础管理;最后还会介绍一个强大的基于Zend Framework和Smarty类库的PHP框架:Hush Framework,本书核心的“微博实例”正是采用这个PHP框架,并使用了其中的MVC分层开发的思路进行开发的。
3.1PHP开发基础
编写本章之前,笔者在考虑一个问题,那就是“如何把一本书的内容压缩到短短的一章中”。这确实是一个难题!但是,虽然篇幅有限,本书还是会尽量使用最简洁明了的语言和最易于理解的实例,来帮助大家以最快的速度认识和了解PHP的开发。
3.1.1PHP语言简介
PHP(Hypertext Preprocessor)是目前最流行的服务端脚本语言之一。近年来,随着互联网的飞速发展,使用PHP语言进行互联网应用开发也变得逐渐火热起来,其特点是简单、快速、灵活,主要应用于各大门户网站、主流CMS平台以及Web 2.0网络应用中,包括Google、Yahoo、Facebook、Zynga在内的互联网巨头们也都大规模地使用PHP作为其主要的编程语言。
那么,PHP究竟能用来做什么呢?一般来说,PHP在实际项目的应用过程中有以下两种主要的使用方式。
1. 用于后台脚本编程,即以命令行(CLI)的方式执行
由于PHP的语法和Linux Shell语言有点类似,而使用起来却要比Shell强大且方便得多,所以我们经常使用PHP作为后台可执行脚本的解决方案。这种方式下的PHP脚本,我们也常称之为CLI(Command-Line Interface)脚本。
2. 用于网络应用编程,即以mod_php或fastCGI的方式执行
简单说就是用于开发网站或者互联网应用,这也是PHP最主要的使用方式。在这种方式中,PHP经常和其他的一些服务端组件结合使用,比如在著名的LAMP架构里,PHP就是与Apache服务器、MySQL数据库组成了互联网应用服务端开发的铁三角。PHP的这种使用方式通常被称为网络(Web)脚本模式。
小贴士:LAMP即Linux、Apache、MySQL以及PHP/Perl/Python相结合的服务端解决方案,也是目前最强大的互联网应用解决方案之一,占据了全球的网站70%以上的市场。简单易用、性能强劲、完全免费这三个特点是LAMP为何如此受欢迎的原因之一。
3.1.2PHP语法简介
了解过PHP语言的用途,接下来我们来看看如何使用PHP。首先,来学习一下PHP基本语法中的重点部分,以下就是“精简版”的PHP语法总结。当然,如果读者已经有过一些其他主流语言的编程经验,比如Java、C++等,那么笔者建议学习时,可以将PHP的语法与这些已经比较熟悉的语言进行对比学习,这样会事半功倍。
1. 规范
PHP代码部分需要用“<?php … ?>”符号框起来,这也表明你可以把PHP代码块嵌入到HTML代码的任何位置,这种用法类似ASP或者JSP。
2. 注释
PHP中单行注释以“//”或者“#”符号开始,多行注释使用“/* … */”符号框起来,这点综合了Perl、C++以及Java语言的用法。
3. 变量
PHP的所有变量都以“$”符号开始,变量的命名规则与C++和Java语言的标准基本相同,例如:$_user是正确的,$@user就是错误的。另外,由于PHP是解释性语言,具有弱类型性,所以PHP的变量不需要声明类型,这点与Java和 C++这些编译型的强类型语言是不同的。
4.常量
PHP使用define函数来定义常量,这点类似于C和C++语言。常量名我们一般都会使用全大写的字母,比如“define('CONSTANT', $constant);”这行代码就定义了一个值为$constant的CONSTANT常量。
5. 函数
自定义PHP的函数必须包含function关键字,比如“function hello () {...}”。此外,PHP语言的自带函数库是非常强大的,这点大家可以在日后使用中慢慢体会。
6. 类定义
定义PHP类的方法和Java基本一致,比如“public class User {...}”。另外,在PHP 5发布后,PHP的面向对象功能越加强大,具体可参考本章3.1.4节的内容。
7. 允许文件中包含文件
在PHP中允许包含其他的PHP文件,这样方便了我们进行代码的封装,一般来说使用require和include方法来包含。如果要避免重复包含的问题,则可以使用require_once和include_once方法。
8. 命名空间
对于大型的项目来说,命名空间(Namespace)的功能还是非常必要,使用命名空间可以减少因为类名或者函数名相同所带来的风险。在PHP的新版本中(PHP 5.3),已经支持namespace语法,比如“namespace Core\Lib1”。
事实上,PHP的语法源自Perl语言,并融合了Java和C语言的部分优点,对于有一定编程基础的开发者来说上手非常快。首先,我们来观察一个PHP的Hello World程序,如代码清单3-1所示。
代码清单3-1
<?php
// 打印字符串
echo "Hello World";
?>
从这段代码中我们可以看到一个标准PHP脚本的写法、打印字符串的方法echo,以及单行注释的写法。
小贴士:在实际开发时,我们经常把PHP文件最后的“?>”符号去掉,因为这样写不仅不会影响PHP的语法解释,还可以避免一些由于编辑器在文件的末尾处自动加上特殊字符,从而影响PHP解释和输出的问题。
接下来,我们来分析代码清单3-2中的PHP的程序范例。代码逻辑非常简单,最前面定义了一个名为“USERNAME”的常量,接着定义了一个函数isJames()用于判断输入的参数是否等于“James”,最后打印函数的测试结果。很显然这段代码的运行结果是false,因为传入值和比较值的大小写是不一样的。
代码清单3-2
<?php
// 常量定义
define('USERNAME', "James");
// 函数定义
function isJames ($username) {
if (USERNAME == $username) {
return true;
}
return false;
}
// 打印结果
var_dump(isJames("james"));
?>
以上代码包含了PHP语言中的注释、变量、常量以及函数等重要语法的使用方法,大家可以尝试在本地运行该脚本。运行方法很简单,直接使用php可执行文件执行即可,比如该php文件名为demo1.php,用户直接在系统命令行窗口中输入“php demo1.php”并运行即可。当然,在此之前我们还必须把php可执行文件的路径加入到系统环境变量中去,否则系统可能提示找不到php命令。代码清单3-2的运行结果如图3-1所示。
图3-1代码清单3-2的运行结果
3.1.3PHP开发起步
通过3.1.2节的学习,我们大致了解了PHP的基本语法,本节我们将进一步认识PHP语言。首先,我们来看看PHP语言的几个特色,也就是它和其他语言不大一样的地方。
1. 预定义变量
PHP提供大量的预定义变量,准确来说应该是预定义“数组”变量,用于存储来自服务器、运行环境和输入数据等动态信息,不同于其他语言使用对应的使用类包或者方法来获取的方式,相对来说PHP的这种方式更加简单直接。表3-1中列出了PHP语言中比较重要的预定义变量。
表3-1PHP的重要预定义变量
变量名环境作用
$GLOBALS—引用全局作用域中可用的全部变量
$_SERVER—服务器和执行环境信息
$_GETWebHTTP GET 变量
$_POSTWebHTTP POST 变量
$_FILESWebHTTP 文件上传变量
$_COOKIEWebHTTP Cookies
$_SESSIONWebSession 变量
$_REQUESTWebHTTP Request 变量(包含HTTP GET/POST)
$_ENV—系统环境变量
$http_response_headerWebHTTP 响应头
$argcCLI传递给脚本的参数数目
$argvCLI传递给脚本的参数数组
以上这些预定义变量都是我们在开发过程中经常使用到的,但是需要注意的是它们中某些变量的使用环境是有限制的,比如$_GET、$_POST以及$_SERVER变量在CLI模式下是没有作用的,因为CLI不运行在服务器环境里。关于这点我们已经在上表的“环境”一列中说明了,其中标注Web的表示只有在网络脚本模式下才能使用;CLI表示只工作在CLI脚本模式下;其他的预定义变量在两种模式下均可使用,但是数组的内容可能会不同。
以下是一个使用预定义变量的例子,不像Java还需要使用request.getParameter()方法逐个获取GET参数,PHP会直接把所有的GET参数全部放到预定变量$_GET中,我们可以直接循环打印出来,如代码清单3-3所示。
代码清单3-3
<?php
$sp = "
\n";
foreach ((array) $_GET as $k => $v) {
echo "GET $k : $v".$sp;
}
?>
由于这个脚本必须在Web模式下才能使用,因此我们需要把以上代码放入站点目录下,开启浏览器,运行结果如图3-2所示。
图3-2代码清单3-3的运行结果
2. null、false、0和空字符串之间的关系
在PHP中,如果一个变量没有赋值则为null,这点和Java类似;但是,PHP是一门“弱类型”的语言,因此对于PHP来说,null和false、0以及空字符串之间的关系有点特殊,如代码清单3-4所示。
代码清单3-4
<?php
// null and 0
echo "null==0 : ";
echo var_dump(null==0);
// null and ''
echo "null=='' : ";
echo var_dump(null=='');
// null and false
echo "null==false : ";
echo var_dump(null==false);
// null and 0
echo "null===0 : ";
echo var_dump(null===0);
// null and ''
echo "null==='' : ";
echo var_dump(null==='');
// null and false
echo "null===false : ";
echo var_dump(null===false);
?>
以上脚本的运行结果如图3-3所示。我们来分析一下结果:首先,前3段代码说明了在PHP中null、false、0和空字符串之间是可以画等号的,这是因为它们在PHP都属于一种“非”类型,其他的“非”类型还包括空数组等;其次,看后3段代码,我们又可以发现,如果我们要区别这几个值也是可以做到的,在PHP中我们使用全等号便可以判别出它们之间的不同。我们在日常编码时一定要注意PHP的这个特性,如果随意乱用,很有可能会犯一些低级错误。
图3-3 代码清单3-4的运行结果
表3-2列出了PHP语言中对于“非”类型的汇总信息,大家在使用PHP的过程中一定要特别注意这些数值的使用方法。
表3-2 PHP的“非”类型汇总
数值作用
null当变量未被赋值,则为null
0整型数值
false布尔类型
空字符串字符串"和""
空数组空数组Array()
3. 魔术变量和魔术方法
最初PHP魔术变量的出现主要是为了方便开发者调试PHP的代码;当然,我们也可以利用这些魔术变量来实现特殊需求。在写法方面,魔术变量前后都有两个下划线,接下来让我们来熟悉一下这些变量。
__LINE__:返回文件中的当前行号,我们在定位错误的时候经常用到。
__FILE__:返回当前文件的完整路径和文件名。自PHP 4.0.2起,__FILE__总是包含一个绝对路径(如果是符号连接,则是解析后的绝对路径)。
__DIR__:返回当前文件所在的目录(PHP 5.3.0中新增)。如果用在被包括文件中,则返回被包括的文件所在的目录,等价于dirname(__FILE__)。除非是根目录,否则目录中名称不包括末尾的斜杠。
__FUNCTION__:返回当前函数的名称(PHP 4.3.0中新增)。自PHP 5起本常量返回该函数被定义时的名字,大小写敏感。在PHP 4中该值总是小写字母的。
__CLASS__:返回当前类的名称(PHP 4.3.0中新增)。自PHP 5起本常量返回该类被定义时的名字,大小写敏感。在PHP 4中该值总是小写字母的。
__METHOD__:返回当前类的方法名(PHP 5.0.0中新增)。注意与__FUNCTION__的返回有所不同,大小写敏感。
__NAMESPACE__:返回当前命名空间名(PHP 5.3.0中新增)。这个常量是在编译时定义的,大小写敏感。
魔术方法主要是随着PHP的面向对象特性出现的(也就是在PHP 5之后),主要解决的是PHP在面向对象的思想中所遇到的一些特殊情况,写法方面和魔术变量类似,魔术方法使用两个下划线开头,接下来学习常用的魔术方法。
__construct():通用的类构造函数。
__destruct():通用的类析构函数。
__get(string $name):当试图读取一个并不存在的类属性时被调用。
__set(string $name, mixed $value):给未定义的类变量赋值时被调用。
__call(string $name, array $arguments):当调用一个不可访问类方法(如未定义或不可见)时,__call() 会被调用。
__callStatic(string $name, array $arguments):当调用一个不可访问的静态类方法时,__callStatic()方法会被调用。
__toString():当打印一个类对象时被调用,这个方法类似于Java的toString方法。
__clone():当类对象被克隆时调用。
__sleep():持久化一个类对象时,如果__sleep()方法存在则先被调用,然后才执行序列化操作。这个功能可以用于清理对象,比如你有一些很大的对象,不需要持久化,这个功能就很好用。
__wakeup():与__sleep()相反,在反持久化类对象时,如果存在__wakeup()方法,则使用该方法预先准备对象数据。__wakeup()可用在类似于重新建立数据库连接等初始化操作中。
__isset():当对未定义的类变量调用isset()或empty()时,__isset()会被调用。
__unset():unset一个对象的属性时被调用。如:unset($class->name)。
__invoke():当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用。
__autoload():区别于以上所有方法,__autoload()并非是一个类方法,而是一个全局方法。在实例化一个对象时,如果对应的类不存在,则该方法被调用,可用于类的自动加载。
另外,别忘了所有的魔术方法都需要给予public属性。关于魔术变量和魔术方法的应用如代码清单3-5所示。
代码清单3-5
<?php
class ClassA {
// 私有变量
private $secret;
// 给私有变量赋值
private function setSecret () {
$this->secret = "my secrets";
}
// 构造函数
public function __construct () {
echo "CALL ".__METHOD__."\n";
$this->setSecret();
}
// 析构函数
public function __destruct () {
echo "CALL ".__METHOD__."\n";
}
// 魔术方法 __get
public function __get ($name) {
echo "CALL __get:".$name."\n";
}
// 魔术方法 __set
public function __set ($name, $value) {
echo "CALL __set:".$name.",".$value."\n";
}
// 魔术方法 __call
public function __call ($name, $arguments) {
echo "CALL __call:".$name.",".print_r($arguments, true)."\n";
}
// 魔术方法 __sleep
public function __sleep () {
echo "CALL ".__METHOD__."\n";
$this->secret = "unknown";
return array("secret");
}
// 魔术方法 __wakeup
public function __wakeup () {
echo "CALL ".__METHOD__."\n";
$this->setSecret();
}
}
$a = new ClassA();// 初始化 ClassA
$a->attrA = "valueA";// 赋值不存在的属性 attrA
echo $a->attrB;// 获取不存在的属性 attrB
$a->hello(1,2,3);// 调用不存在的方法 hello()
$b = serialize($a);// 持久化 ClassA
var_dump($b);// 打印持久化后的对象
$c = unserialize($b);// 反持久化 ClassA
var_dump($c);// 打印反持久化后的对象
?>
以上程序先定义了一个类ClassA,此类定义了一个$secret属性,还定义了我们上面介绍到的一些主要的魔术方法,接下来执行了以下步骤,我们来逐一分析。
1)初始化 ClassA:调用构造函数__construct(),并对$secret变量赋值。
2)赋值不存在的属性 attrA:调用魔术方法__set()。
3)获取不存在的属性 attrB:调用魔术方法__get()。
4)调用不存在的方法 hello():调用魔术方法__call()。
5)持久化 ClassA:调用魔术方法__sleep(),并隐藏$secret变量的值。
6)反持久化 ClassA:调用魔术方法__wakeup(),并恢复$secret变量的值。
7)最后回收对象:调用两次析构函数__destruct(),原因是这里产生了两个对象实例,$a和$c。
该程序的最终运行结果如图3-4所示,大家可以结合上面的分析来思考。
图3-4代码清单3-5的运行结果
小贴士:代码清单3-5中的print_r方法的功能主要用于打印PHP数组。
4. 神奇的PHP数组
记得多年以前有位做Java开发的同事曾经问过我这样一个问题:“为什么PHP的数组这么好用呢?”当时我觉得不以为然,不过现在回过头来想想,这个问题确实值得好好讨论一下。要解决这个问题,首先我们要理解一点:对于PHP来说,没有集合(Set)、栈(Stack)、列表(List)以及散列(Hash)的概念,所有这些常见的数据结构都在PHP的数组里面实现了。我们先来看一段关于PHP数组操作的代码,如代码清单3-6所示。
代码清单3-6
<?php
$arr = array(1,2,3);
// 集合用法
echo '$arr[0]:'.$arr[0]."\n";
// 栈用法
array_push($arr, 4);
echo '$arr:'.print_r($arr, true);
array_pop($arr);
echo '$arr:'.print_r($arr, true);
// 列表用法
array_push($arr, 4);
echo '$arr:'.print_r($arr, true);
array_shift($arr);
echo '$arr:'.print_r($arr, true);
// 散列用法
$arr[3] = 5;
echo '$arr[3]:'.$arr[3]."\n";
?>
从代码中的注释可以看出,以上程序分别模拟了集合、栈(后进先出)、列表(先进先出)以及散列数组的用法,大家可以体会一下,该程序的运行结果如图3-5所示。
图3-5代码清单3-6的运行结果
之前我们讨论的都是最为简单的一维数组,而在实际项目中我们经常使用的是其他组合形式的数组,比如,和数据库表结构所对应的“散列数组列表”的形式,下面我们再来看一个例子,见代码清单3-7。
代码清单3-7
<?php
// 散列数组列表
$arr = array(
array(
"name" => "James",
"sex" => "M",
"age" => "28"
),
array(
"name" => "Iris",
"sex" => "F",
"age" => "27"
)
);
?>
<table border=1 cellspacing=1 cellpadding=5>
<tr><td>Name</td><td>Sex</td><td>Age</td></tr>
<?php foreach ($arr as $row) { ?>
<tr><td><?=$row["name"]?></td><td><?=$row["sex"]?></td><td><?=$row["age"]?></td></tr>
<?php } ?>
</table>
在上述例子中,我们演示了一个“散列数组列表”的使用方法,实际上这种数据结构经常出现在我们从数据库中取出数据然后展现到页面表格中去的情况。除此之外,从本例中我们还可以学到如何在PHP中嵌入HTML标签,当然这已经是一种最古老的使用方法了,在实际项目中我们经常使用一些其他的模板引擎来负责展示部分,比如Smarty模板引擎(我们将在3.5节中介绍)。最后我们来看一下本例在浏览器中的运行效果,如图3-6所示。
通过以上的介绍和实例学习,相信大家对PHP的数组已经有了一定的了解,这种融合了列表、散列、栈等多种常用数据结构的“超级工具”可以说是PHP的一大发明;当然,PHP数组的用法绝不仅仅只有以上这些,通过日后继续深入学习,我相信你会越来越喜欢这个“编程利器”的。
3.1.4PHP面向对象编程
虽然之前我们已经学习了PHP语言的开发基础,但是应用这些知识来开发项目还远远不够,因为我们在实际的项目中都需要使用“面向对象”的写法来进行编程,如果只懂得基本语法却没有面向对象的编程思想是万万不行的。所以接下来我们将为大家介绍在PHP语言中,我们是如何使用面向对象的编程思路来进行开发的。
其实PHP语言的面向对象编程思路和Java非常相似,对象(Object)、类(Class)、抽象类(Abstract Class)、接口(Interface)以及MVC三层封装的用法都大同小异,由于篇幅限制,相同的地方将不再赘述,这里主要给大家介绍一下不同的地方,下面我们将结合面向对象编程思想中的几个重要概念来分析一下。
1. 抽象性
要认识面向对象的思想,首先需要理解什么是对象。对象是面向对象编程的基本元素,对象可以是我们需要研究的任何元素,可以是具体的物品,也可以是抽象的事物。比如我们在研发一个后台管理系统时,管理员是一个对象,后台权限也是一个对象。这就是抽象的基本思路,就这点来说,不管我们使用的是什么语言,思考方式应该是一样的。
此外,对象在程序代码中是用“类”(Class)来表示的,每个类都需要具备“唯一性”的特征,在大部分语言中都使用“命名空间”来解决这个问题;然而对于PHP来说,我们通常使用一种“约定”或者“规范”来解决这个问题,比如在PHP的一些大型类库(如Zend Framework)中我们通常把类名和目录名对应起来,Zend/Db/Exception.php文件的类名是Zend_Db_Exception,这种方式在项目中还是比较实用的。
2. 继承性
继承性是面向对象思想中最基础、最重要的特点之一,也正是因为对象的继承性才延伸出了“抽象”和“封装”等面向对象的设计方法。在PHP语言中我们同样使用私有(private)、保护(protected)和公共(public)关键字来设定类、属性和方法的权限,关于这些基础概念的基本用法大家可以到网上收罗到一大堆,这里不再赘述。下面我们将以一个PHP代码为例给大家讲解一下使用要领,见代码清单3-8。
代码清单3-8
// 基础抽象类
abstract class Base_Customer {
// 私有属性
private $_name = '';
// 私有方法
private function _checkName ($name) {
return is_string($name) ? $name : false;
}
// 保护方法
protected function _formatName ($name) {
return (string) $name;
}
// 公用方法
public function setName ($name) {
$this->_name = $this->_formatName($name);
}
// 公用方法
public function getName () {
return $this->_checkName($this->_name);
}
// 抽象方法,子类需要实现
abstract public function setPassword();
// 抽象方法,子类需要实现
abstract public function getPassword();
}
上述实例代码中我们定义了一个名为Base_Customer的用户抽象基类,里面有一个名为“$_name”的私有属性,表示用户的名字;为了让外部能够访问这个属性,我们添加了读取名字getName和设置名字setName两个public方法,供外部程序调用。另外,此类还有一个private方法_checkName用于确认用户名字是否有误,一个protected方法_formatName用于保证名字正确性。在代码的最后我们还定义了两个抽象方法,设置密码setPassword和取得密码getPassword两个方法,用于对用户的密码进行操作,这两个方法都需要在子类中实现的。从以上代码中我们可以看到PHP语言里大部分面向对象编程的写法,大家可以好好理解一下。
3. 多态性
多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果,这个特性进一步增加了面向对象编程思想的灵活性和重用性。我们知道,重载是实现多态性最常见的方法,比如下面就是使用Java语言来使用重载的实例代码,见代码清单3-9。
代码清单3-9
class Demo{
// 成员变量
int a, b, c;
// 构造函数
public Demo() {}
// 重载构造函数
public Demo(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
// 成员方法
int sum() {
return a + b + c;
}
// 重载成员方法
int sum(int d) {
return a + b + c + d;
}
}
我们看到上面用Java语言实现的Demo类中,构造方法Demo被定义了两次,根据传入参数的不同,方法逻辑也有所不同;另外,类中的成员方法sum也被定义了两次,同样可以根据不同的参数来处理不同的逻辑。但是这是Java的做法,在PHP中我们也可以这样做吗?答案是否定的,因为PHP语言不允许出现相同名称的方法,即使在同一个类中。那么我们应该怎么做呢?看看代码清单3-10中是怎么写的吧。
代码清单3-10
class Demo {
// 成员变量
public $a, $b, $c;
// 构造方法
public function Demo ($a = 0, $b = 0, $c = 0) {
if ($a) $this->a = $a;
if ($b) $this->b = $b;
if ($c) $this->c = $c;
}
// 成员方法
public function sum ($d = 0) {
if ($d) {
return $this->a + $this->b + $this->c + $d;
} else {
return $this->a + $this->b + $this->c;
}
}
}
我们可以看到,以上的PHP代码同样实现了一个Demo类,此类含有和前面Java版的Demo类同样的成员变量,却只有一个构造方法Demo和成员方法sum;但是有趣的是,通过对这两个方法的逻辑分析,我们会发现这里同样根据参数的不同实现了不同的逻辑。这是为什么呢?答案其实很简单,就是因为PHP允许设置参数的默认值。这种PHP特有的功能帮助我们用另外一种方式实现了多态性。
以上我们介绍的PHP面向对象编程的基础知识,需要大家好好理解一下,因为在后面我们即将给大家介绍的微博应用实例中,这些用法将会被广泛使用;另外,在本章最后介绍的Hush Framework框架中我们也会接触到这些用法。当然,培养成熟的面向对象的编程思想绝不是一朝一夕的事情,需要大家在学习的时候边实践边思考,最好能阅读一些比较优秀的代码,当然本书后面将要给大家介绍的微博项目实例代码也是个很不错的面向对象编程的代码范本,如果大家能通过本书将其理解透彻,绝对会受益匪浅。
3.1.5PHP的会话
业内常说“不理解会话(Session)的概念就等于不懂得PHP网络编程”。当然,这里讲的是PHP语言用于互联网编程的时候。因为HTTP协议是无状态的,所以每次请求结束后,变量和资源就被回收了,那么我们如何保存一些“持久”的信息呢?比如在用户登录之后,系统需要把用户登录的信息保存下来,在整个应用或者站点里面使用。也许你会想到使用数据库来保存,当然这确实是一种解决方案,但是这些数据大部分属于临时数据,用户退出登录之后就没有用了,如果使用数据库不仅浪费资源,而且还需要定期清理,相当麻烦。为了解决这个问题,PHP专门为我们提供了会话模块,来保存这些临时的用户数据。
和大部分的语言环境一样,PHP的Session机制不是非常复杂,客户端只需要保存一个会话ID,即Session ID,每次会话请求都会把这个Session ID传给服务端,并获取服务端接口处理完的数据,整个过程如图3-7所示。
图3-7PHP的Session机制
PHP默认的会话存储方式是文件存储,数据会被保存到服务器本地的session.save_path参数设定的目录中(此参数位于php.ini配置文件)。使用的时候,首先需要调用session_start方法开启一个新的Session,然后直接使用PHP预定义变量$_SESSION来进行读取和存储操作,在请求结束时系统会把修改过的会话值保存到存储器中。示例用法如代码清单3-11所示。
小贴士:php.ini是PHP的环境配置文件,在Linux系统下一般会被放在/etc/php.ini目录下。该文件几乎包含了PHP运行环境所需的所有配置,也是我们必须学习的内容之一,由于篇幅原因,本书不做详细讲解。具体配置参数可直接参考官方文档,地址如下http://cn.php.net/manual/zh/ini.list.php。
代码清单3-11
<?php
session_start();
// 初始化计数器变量 $count
$count = isset($_SESSION['count']) ? $_SESSION['count'] : 0;
// 计数器依次递增
$_SESSION['count'] = ++$count;
// 打印计数器值
echo $count;
前面的会话实例实现了一个简单的计数器,逻辑很简单,大家参照着注释就可以很快读懂。这里需要注意的是,Session机制仅适用于有服务器的网络环境中,在以命令行(CLI)脚本运行的情况下是不起作用的。另外,我们可以看到该计数器程序的有效代码只有4行,这也从一个侧面反映了PHP语言的简单高效。
当然我们这里讨论的仅仅是比较简单的Session使用场景,对于相对比较大型一点的网络应用来说,Session的使用就不是这么简单了。比如我们要在多台应用服务器之间共享Session,那就不能把Session信息存放在本地了,这时候我们可能需要把Session集中存储在某个公用的中间件里,比如数据库或者缓存服务器等。好在PHP给我们提供了Session回调接口来帮助我们控制Session的存储方式,实例代码请参考代码清单3-12。
代码清单3-12
<?php
class SessionHandler {
protected $savePath;
protected $sessionName;
public function __construct() {
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
}
public function open($savePath, $sessionName) {
$this->savePath = $savePath;
$this->sessionName = $sessionName;
return true;
}
public function close() {
// 关闭 Session 逻辑
return true;
}
public function read($id) {
// 读取 Session 逻辑
}
public function write($id, $data) {
// 存储 Session 逻辑
}
public function destroy($id) {
// 销毁 Session 逻辑
}
public function gc($maxlifetime) {
// 回收 Session 逻辑
}
}
// 使用 Session 类
new SessionHandler();
上述实例中,我们使用session_set_save_handler方法重写了PHP的Session机制,通过这种方式我们可以很方便地控制Session的存取逻辑来满足我们的需求;此外,这也是我们优化Session机制时必须使用的知识,这些进阶知识我们会在本书的9.1.2节中给大家做进一步的介绍。
3.2PHP开发环境
前面我们已经学习了PHP编程语言的基础知识,接下来我们来了解PHP的开发环境。在此之前,我们先讨论一下PHP的开发工具。PHP是一种脚本语言,因此就语言本身特点而言,对开发工具没有什么严格的限制,从简单的Notepad和EditPlus到复杂的Zend Studio和Eclipse都可以进行PHP开发;但是在实际项目中,为了保证编码的一致性,以及代码版本管理的方便性,我建议大家在项目开发时使用Eclipse作为PHP编程开发的统一工具,如此一来,还可以和Android应用开发使用同一个开发工具,何乐而不为呢?
3.2.1开发环境的搭建
让Eclipse支持PHP有两种方式,其一是在本机的Eclipse开发工具上安装一款名为PHPEclipse的插件;不过现在我们一般使用另一种方式,也就是直接下载Eclipse专门为PHP开发者定制的开发工具PDT,下载地址为:http://www.eclipse.org/pdt/downloads/。
PDT的安装方法很简单,解压即可使用。但要注意的是,如果你的开发机之前没有安装过Java运行环境,PDT还是不能运行的,毕竟它还是要依靠Eclipse环境(环境搭建过程可参考2.10.1节)。当然,如果你想要把PHP开发环境和Android开发环境合为一体也是可以的,这就需要我们先下载PDT解压安装之后再安装ADT。
3.2.2安装配置Xampp
和Android客户端开发不同,进行PHP服务端开发,除了要安装语言本身的环境之外,还需要安装和配置服务端需要的组件,这也是服务端开发和客户端开发的不同之处。当然,PHP的集成开发环境有很多,本书为大家推荐一个方便实用的集成开发环境套件:Xampp。该套件是完全免费的,它集成了Apache服务器、MySQL数据库、PHP语言以及PERL语言等我们常用的服务端开发工具。
Xampp的下载地址非常多,利用搜索引擎可找到很多关于“Xampp下载”的链接,大家选择一个比较官方的链接点击下载即可。当然在本节中我们只会重点介绍这个工具的使用方法,如果你想了解更全面的关于Xampp开发环境套件的详细内容,可以上官方网站了解,网址为:http://www.apachefriends.org/zh_cn/xampp.html。
本书的开发环境是Windows,所以在下载完Xampp的Windows版本之后,我们需要将其安装到一个便于访问的目录下,比如D:\xampp目录,其中包含的文件如图3-8所示。
图3-8Xampp目录下包含的文件
从图3-8中我们可以看到Xampp还提供了很多额外的配套工具,我们先不看这些工具,找到“xampp-control.exe”文件,双击打开,会看到如图3-9所示的Xampp控制台界面。
在Xampp的控制台界面中,我们可以看到前两排分别是Apache和MySQL的控制按钮,我们单击这两个“Start”按钮就可启动Apache和MySQL了,非常方便。接着我们打开浏览器,输入“http://localhost”地址就可以看到Xampp的管理界面了,如图3-10所示。
Xampp管理界面可以支持多种语言,若要使用中文可以从页面右上方的语言选项中选择。另外,界面的左边是Xampp所有的功能选项,接下来,介绍其中比较重要的几个管理工具。
状态:Xampp主要组件的运行状态。
图3-9Xampp控制台
图3-10Xampp管理界面
安全:如果你想用Xampp作为正式环境,这个部分就很重要,因为这里涉及一些关于Xampp安全的注意事项。
文档:Xampp常用组件的文档,包括Apache、MySQL等。
phpinfo():此选项查看的是PHP的系统参数,比如,如果我们需要查找一些PHP的模块是否已经安装就可以在这里查看。
phpMyAdmin:MySQL数据库管理工具,关于此工具将在本章的3.2.4小节中做详细介绍。
Webalizer:简单小巧的Web日志分析工具,可做简单的访问分析。
Mercury Mail:Mail服务器,建议仅供调试。
FileZilla FTP:FTP服务器,建议仅供调试。
Xampp的管理工具看起来非常多,然而,在开发过程中经常用到的管理工具并不多,最经常用到的无非就是使用“phpinfo()”查看PHP环境参数,以及使用“phpMyAdmin”管理MySQL数据库等。
3.2.3管理Apache
Apache服务器是当今功能最为强大的HTTP服务器之一,也是目前全球市场占有率最高的HTTP服务器。因此,对于服务端开发者来说,如何管理Apache应该算是一个必须学习的内容;当然,如果你想仅仅通过本节就完全掌握Apache这是绝对不可能的,因为仅仅是Apache的日常管理文档就可以写成一本很厚的参考书。本节我们主要介绍一下在PHP服务端开发过程中,Apache服务器的基本用法。
由于Xampp环境已经帮助我们把Apache和PHP结合起来了,所以不需要做任何配置就可以让Apache支持PHP脚本。以下是一些在日常开发过程中常出现的操作,让我们来分别学习一下。
1. 启动和停止
在Xampp中启动和停止Apache非常简单,可直接在Xampp控制台中进行操控。如果有疑问可以参考3.2.2节的内容。
2. 设置虚拟主机(Virtual Host)
当我们开发一个新的网络应用时,首先,我们需要给这个网络应用分配一个域名,那么我们怎么在开发机上访问这个域名呢,我们假设现在要做的网络应用的域名是“http://test-app”,我们可以通过以下步骤来设置Apache的虚拟主机。
首先,我们需要找到并打开Windows本地的host文件,该文件位置如下:C:\WINDOWS\system32\drivers\etc\hosts,并在文件尾部加上如配置清单3-1所示的内容。
配置清单3-1
…
127.0.0.1 test-app
…
然后,我们再打开Apache的虚拟主机配置文件,该文件位于Xampp中的Apache目录下,如D:\xampp\apache\conf\extra\httpd-vhosts.conf,其中我们加入如配置清单3-2所示的配置信息。
配置清单3-2
…
NameVirtualHost *:80
<VirtualHost *:80>
DocumentRoot "/path/to/test-app"
ServerName test-app
<Directory />
AllowOverride All
Allow from all
</Directory>
</VirtualHost>
…
接着,我们在站点根目录(DocumentRoot)中放入一个测试PHP脚本,用于测试环境是否配置成功,如代码清单3-13所示。
代码清单3-13
<?php
phpinfo();
?>
最后,重启Apache服务器,打开浏览器并输入刚才准备好的PHP脚本文件进行测试,效果如图3-11所示。
图3-11info.php运行结果
如果看到的页面和上图的一样,那么表示我们的PHP网络脚本开发运行环境准备就绪了。学会这些之后,你还可以在该应用目录下编写其他的PHP脚本进行学习。
3.2.4管理MySQL
MySQL数据库绝对是现在市面上最为流行的开源数据库之一。实际上,PHP和MySQL在很早以前就被认为是互联网领域的“天作之合”,PHP为MySQL提供了非常稳定而高效率的数据库接口,而MySQL又为PHP提供了灵活而强大的数据存储方式,所以在学习PHP的同时,MySQL也就自然而然变成必学内容中的一部分了。
和Apache一样,MySQL同样是一个庞然大物,想用一节的文字就把MySQL完全说清楚同样是不大现实的事情,因此在本节中我们只对MySQL本身做简单介绍,主要介绍如何使用phpMyAdmin工具来管理MySQL数据库。
首先,我们来简单介绍一下MySQL数据库。和本书中所介绍的其他组件一样,MySQL是开源而且免费的,除此之外,它还有以下几个主要的优势和特点。
1. 稳定性
对于数据库来说,稳定性毫无疑问是最重要的。对于MySQL的稳定性,其实无须多虑,作为目前全球最受欢迎的开源数据库,MySQL被无数的互联网应用所采用,比如Facebook等。而在这些成功的实例中,MySQL扮演着最稳定的数据存储后盾的角色。
2. 高性能
支持多线程,性能佳,同时MySQL还提供了非常丰富的性能配置选项(在配置文件my.cnf中)。我曾经对目前Linux上的多个主流数据库做过高并发的压力测试,MySQL的处理能力绝对是名列前茅的。
3. 灵活性
单台MySQL服务器支持的对象数达到十亿(Billion)级别,因此从理论上来讲,在性能没有下降的前提下,我们可以建立任意多个数据库,每个数据库中包含任意多张数据表,这样我们就可以在一台MySQL服务器上模拟分库分表,当然,我们甚至还可以在一台服务器上建立多个MySQL实例。
4. 支持主从
主从复制(Replication)也是MySQL最重要的特性之一,MySQL支持一主多从,以及互为主从两种模式。我们常用的是一主多从的方式,在主从模式运行时,主库会持续地把数据同步到从库上去,一般来说我们会将主库作为写库而从库作为读库,这样做的好处是:多个从库不仅可以为主库分担读的压力,而且还可以为主库提供多套数据备份,当主库出问题时,我们可以通过修改配置快速地进行数据恢复。
5. 支持集群
在MySQL 5之后也支持使用NDB Cluster存储引擎来实现多Cluster的服务器集群,但是在PHP项目中通常依靠程序逻辑来实现数据库集群的功能。
6. 插件丰富
据我了解MySQL的插件应该是目前所有数据库中最多的,针对各种不同的使用场景,都会有不同的数据库引擎或者数据库插件与之对应,比如近几年出现的MySQL的NoSQL处理引擎HandleSocket等。丰富的插件系统也使得MySQL的应用范围越来越广。
接下来,我们来看看在Xampp环境下如何方便地管理MySQL。在3.2.2节中曾经提到过Xampp自带的phpMyAdmin管理工具,此工具是由纯PHP写出来的,特点就是部署完之后可以直接在浏览器中打开操作,界面如图3-12所示。
图3-12展示的就是phpMyAdmin的主界面(在不同的版本里phpMyAdmin的界面表现可能会稍有不同,但是功能布局肯定是不会变的),左边灰色的列表就是目前所有的MySQL数据库列表,其中除了mysql、information_schema、performance_schema以及test是MySQL自带的数据库之外,其他的数据库都是后来添加的。我们单击对应的数据库名就可以进入对应的数据库管理界面,例如我们单击cdcol数据库,会看到如图3-13所示的管理界面。
从图3-13中可以看到,cdcol库中只有一个表cds,单击表名就可以在右边看到表里所有数据的列表,当然我们可以对这些数据进行增删查改等动作。另外,在数据列表上面我们可以看到所有操作的相关SQL,非常方便;SQL栏上方还有一排按钮选项,这些选项的功能也是我们在日常操作中经常使用的,下面简单介绍一下。
图3-12phpMyAdmin主界面
图3-13phpMyAdmin数据表管理
浏览:默认的功能,用于管理表中的数据。
结构:用于查看表的详细结构,还可以添加索引。
SQL:使用我们自己编写的SQL语句进行数据表操作。
搜索:快捷地使用模糊搜索查找数据。
插入:插入新的数据。
导出:导出表中的数据,一般用于数据备份或者转移;phpMyAdmin提供了非常多的导出方式和选项,一般来说MySQL导出的文件都是文本SQL文件。
导入:和导出相反的功能,一般用于数据恢复。
操作:提供一些其他的高级功能选项,比如修改数据表名、修改存储引擎、修改字符集等操作,需要了解更多信息请进入相应界面查看。
清空:清空表内所有数据,此操作在未开启事务的情况下不可恢复,请慎用!
删除:删除整张表,请慎用!
由于篇幅限制,对于phpMyAdmin的一些主要功能的介绍到此为止,如果你想熟悉这个工具建议动手操作一下,熟悉一下这个MySQL管理工具的日常功能,这对后面的服务端开发是非常重要的。
3.3使用JSON通信
实际上,第1章中介绍如何结合Android和PHP学习时,我们就曾经提到过JSON协议,本节我们就来学习一下这个协议的基本内容。JSON是JavaScript对象表示法(JavaScript Object Notation)的简称,JSON协议源自JavaScript脚本语言的对象持久化表示方法,由于这种表示法比较简单易懂,而且传输的数据也比较小巧(相对于XML来说应该算是非常小巧了),因此,近年来被广泛地用于互联网应用的数据封装。
首先,我们来学习一下JSON协议的数据表示方法。在JSON协议中,最基本的数据结构只有两种。第一种是数组结构,该结构类似于PHP中的列表数组,结构如下。
["james","iris"]
第二种是对象结构,该结构非常类似于PHP中的散列数组,结构如下。
{"id":1,"name":"james"}
当然,将以上两种结构结合起来就可以产生其他形式的数据结构,比如对象数组,也就是类似于PHP中的“散列数组列表”的形式,结构如下。
[
{"id":1,"name":"james"},
{"id":2,"name":"iris"}
]
另外,JSON协议几乎支持所有主流语言的客户端,当然也包括PHP语言。在PHP中使用JSON非常方便,在PHP 5.2版本之后,PHP语言已经内置了JSON的加解码函数,即json_encode和json_decode。接下来,让我们来分析一下代码清单3-14中的逻辑代码。
代码清单3-14
<?php
// 原始数据
$arr = array(
array(
"id" => 1,
"name" => "James"
),
array(
"id" => 2,
"name" => "Iris"
)
);
// 数组转换为JSON格式
$str = json_encode($arr);
echo "Array => JSON : ".$str."\n";
// JSON转换为数组格式
echo "JSON => Array : ";
$arr = json_decode($str);
print_r($arr);
?>
以上代码演示了如何使用PHP内置的加解码函数来进行JSON数据和PHP数组结构之间的相互转换,运行结果如图3-14所示。
图3-14代码清单3-14的运行结果
这里随便提一下,在Android中我们使用org.json包来进行JSON加解码工作,JSON数组格式可使用JSONArray类处理,而对象结构则使用JSONObject类处理。关于Android使用JSON的具体使用方法和实例我们将在本书第7章的7.3.3节中做详细介绍。
3.4常用PHP开发框架
随着互联网应用的发展,我们过去依靠原生PHP代码进行编程的路已经走不通了。因为,随着代码逻辑越来越复杂,如果没有一个好的框架来管理和组织代码,乱七八糟的巨量代码最终必将把整个项目毁掉,这也是我们在项目结束之后还要花那么大的资源和精力对代码进行不断优化和重构的原因。当然,如果我们在项目开始时就采用一个比较好的框架进行开发,不仅可以让以后的功能扩充变得更加简单,还可以为日后的代码维护减少工作量。目前市面上流行的PHP框架非常多,接下来我们会对其中比较有代表性的几个框架做一些介绍和对比。
1. Zend Framework(http://framework.zend.com/)
Zend Framework简称ZF,是PHP的官方框架,优点是功能强大、结构松散、封装完善,很适合作为二次开发的基础框架,它包含了几乎所有你能想到的关于互联网方面的功能类库。当然,ZF的类库包也很大,框架类库就将近20MB,当然其中大部分我们是用不到的。虽然ZF看起来比较“笨重”,但是它所提供给开发者的自由度是其他框架所不能比较的,针对那些需求比较灵活或者结构比较复杂的大型项目而言,ZF确实不失为一个很好的选择。
笔者建议大家去深入学习ZF框架,最好是能把ZF的主要逻辑和类库的代码读一遍,因为从中你不仅可以学到很多PHP语言的编程技巧,还可以学到很多有用的“设计模式”以及“封装”的技巧,这些知识和经验都会对大家今后的编程之路大有裨益。
此外,本书实战篇中的项目实例将会使用一个基于ZF的框架Hush Framework来进行开发,因此学习ZF对大家来说也是非常必要的。当然,由于本书的篇幅限制,我们不可能在这里把ZF全部给大家讲一遍,但是我们在3.6节中在对Hush Framework进行讲解时会涉及ZF框架的一些内容,建议大家认真阅读和体会,相信其中的知识会对深入学习ZF框架有很大帮助。
小贴士:本书的“封装”指的是一种基于面向对象思想的组织代码的方法,后面会多次提到这个概念,大家在阅读时可以注意一下。
2. CodeIgniter(http://codeigniter.com)
CodeIgniter(CI)也是一个比较老牌的PHP框架,和ZF相反,它非常小巧,核心类库仅有1MB左右,使用起来比较简单,代码框架遵循常见的MVC结构;但是CI的类库封装得还不够精细,某些框架层次感觉设计得过于繁琐;另外,我认为CI的文档做得不是很好,特别是中文的文档,当然这可能是多种原因造成的,但是不可否认的是,这个问题大大阻碍了CI框架在国内的普及。
3. CakePHP(http://cakephp.org/)
CakePHP是一个典型的仿RoR类型的框架,它的脚手架(scaffold)功能还不错,也非常的小巧,类库相对来说设计得主流化一点,但是模板支持部分还做得不够好,另外系统设计的耦合度比较高,如果遇到一些大型项目会有点棘手,比如需要分库分表,以CakePHP目前的做法会比较麻烦。
4. ThinkPHP(http://www.thinkphp.cn/)
ThinkPHP是必须要介绍的,是近几年出现的比较优秀的产品之一。整体来说,ThinkPHP很快,几乎是所有PHP框架中最快的;也很小巧,所有的类包加起来才几百KB;在设计方面相对比较松散,易于学习;另外,文档也相当完善,确实是近年来出现的一个不错的PHP框架,但是面对大型项目同样有一些不方便的地方。
当然,面对如此多的PHP框架,到底应该学习哪一个呢?我们可以这样考虑:如果你只想快速地开发出一个应用,那么可以选择Think、Cake或者CI等敏捷型的开发框架;但是,如果你想要更深入地学习PHP语言,甚至PHP中的各种设计模式,请选择ZF。当然,在后面的章节中,我们将会向大家介绍一个基于ZF和Smarty的PHP开发框架,也就是Hush Framework。
3.5认识Smarty模板引擎
如果你说学过PHP而没学过Smarty模板引擎,我相信所有的面试官都会觉得你在撒谎。虽然PHP语言本身就可以嵌入到HTML页面中去进行数据展现,但是这样做我们不仅需要书写大量的<?php ?>标签,而且在某些地方还需要嵌入大量的冗余代码,另外也不利于逻辑的解耦和分离。所以,在项目中我们还是需要一个专门的模板引擎,而Smarty就是PHP语言在这个领域的不二选择了。
目前,最新的Smarty版本已经出到3.x,应该说与2.x版本相比做了很大的改进,接下来简单介绍一下Smarty的使用。实际上,Smarty的下载包中本来就包含了一些实例代码。首先,从官方下载地址(http://www.smarty.net/download)下载最新的稳定的开发包版本(Latest Stable Release),我们在这里使用的是Smarty 3.1.5版本,该版本必须运行于PHP 5.2以上的版本中。
解压之后,我们把Smarty-3.1.5重新命名为smarty并放入我们前面配置好的站点目录,然后在浏览器中打开demo地址(http://php-demo/smarty/demo/),打开的界面如图3-15所示。
图3-15Smarty demo的运行效果
小贴士:如果你找不到站点目录,请返回查看3.2.3节中Apache配置虚拟主机的部分内容。
以上这个界面就是由Smarty模板引擎渲染出来的页面,其对应的PHP文件的代码,见代码清单3-15,已添加注释,方便读者阅读。
代码清单3-15
<?php
// 包含 Smarty 类库
require('.../libs/Smarty.class.php');
// 初始化 Smarty 对象
$smarty = new Smarty;
// 初始化 Smarty 配置
//$smarty->force_compile = true;
$smarty->debugging = true;
$smarty->caching = true;
$smarty->cache_lifetime = 120;
// 各种变量赋值
$smarty->assign("Name","Fred Irving Johnathan Bradley Peppergill",true);
$smarty->assign("FirstName",array("John","Mary","James","Henry"));
$smarty->assign("LastName",array("Doe","Smith","Johnson","Case"));
$smarty->assign("Class",array(array("A","B","C","D"),array("E","F","G","H"),
array("I","J","K","L"),array("M","N","O","P")));
$smarty->assign("contacts", array(array("phone" => "1", "fax" => "2", "cell"
=> "3"),array("phone" => "555-4444", "fax" => "555-3333", "cell" => "760-1234")));
$smarty->assign("option_values", array("NY","NE","KS","IA","OK","TX"));
$smarty->assign("option_output", array("New York","Nebraska","Kansas","Iowa",
"Oklahoma","Texas"));
$smarty->assign("option_selected", "NE");
// 渲染相应模板
$smarty->display('index.tpl');
?>
从上面的代码我们可以很清晰地看到Smarty的一般使用过程:先初始化Smarty对象,然后配置Smarty参数,接着就是进行各种变量的赋值,最后在模板页面展现出来。对于本例,大家可以看看index.tpl中的模板写法,当然Smarty的用法还是很丰富的,想学好它,最好的老师就是官方文档了,请参考“http://www.smarty.net/docs/en/”。以下我们也简单介绍几种在开发时需要掌握的核心要点。
1. 常用配置选项
在使用Smarty模板引擎之前,我们必须先学习如何配置Smarty的选项。而在Smarty的常见选项中,我们首先必须了解4个最基本的目录选项。
模板目录(template):本目录用于存储模板文件,需要渲染对应文件时把文件相对地址作为参数传入display方法即可。比如,我们有一个模板文件地址位于template/test/index.tpl,那么我们则应当使用“$smarty->display('test/index.tpl');”语句来渲染该模板。
编译模板目录(template_c):本目录主要用于存储Smarty模板引擎产生的模板编译文件,Smarty也正是使用这种方法来提高执行效率的。当然,我们在部署项目时一定要注意该目录必须是可写的。
缓存目录(cache):Smarty允许把展示过的模板缓存起来,使用此功能将进一步提高模板引擎的运行速度。当然,我们还可以通过设置cache_lifetime属性来控制缓存文件的有效时间。
配置目录(configs):这个目录可以用于保存Smarty模板引擎的配置文件,不过在实际项目中使用得比较少,我们经常会把配置放入项目统一的配置目录。
在实际项目中我们经常使用继承和重载的方式来定制和配置我们自己的Smarty模板类。比如,在代码清单3-16中,我们就实现了一个自定义的My_Smarty类,此类中设置了Smarty模板的必要目录和缓存的生效时间。
代码清单3-16
<?php
// 包含 Smarty 类库
require 'Smarty.class.php';
// 定义自己的模板类
class My_Smarty extends Smarty
{
function __construct()
{
// 重载 Smarty 基类
parent::__construct();
// 配置目录
$this->setTemplateDir('/path/to/templates/');
$this->setCompileDir('/path/to/templates_c/');
$this->setConfigDir('/path/to/configs/');
$this->setCacheDir('/path/to/cache/');
// 配置缓存
$this->caching = true;
$this->cache_lifetime = 60;
// 设置默认变量
$this->assign('app_name', 'My App');
}
}
?>
在上述代码中,setTemplateDir方法用于设置模板目录,setCompileDir方法用于设置编译过的中间模板目录,setConfigDir和setCacheDir方法分别用于设置Smarty模板的配置文件和缓存文件的目录。
2. 常用模板语法
Smarty 3.0中的语法实际上和PHP的语法已经比较接近了,使用起来相当方便。接下来让我们来熟悉一下Smarty模板语言的基本用法。首先,我们要知道所有的Smarty的默认界限符号是大括号(当然这个也是可以设置的)。因此,我们可以通过类似于“{$var}”的写法来获取Smarty变量“var”的值。其次,Smarty中为我们提供了大量的字符串辅助标签,非常方便,例如,如果需要把某个变量的首字母大写,使用方法如代码清单3-17所示。
代码清单3-17
{$articleTitlecapitalize}
另外,如果我们想把时间戳转化为需要的时间格式,使用方法如代码清单3-18所示。
代码清单3-18
{$smarty.nowdate_format}
{$smarty.nowdate_format:"%Y-%m-%d"}
此外,我们还可以使用代码清单3-19中的类似方法来过滤非法字符,避免XSS(跨站攻击)的风险。
代码清单3-19
{$articleTitleescape:'html'}
{$articleTitleescape:'htmlall'}
接下来,我们还会介绍一下在展示过程中最常用到的循环语句的写法。实际上在Smarty中有两种最常用到的循环语句写法,一种是“{section}”,另一种是“{foreach}”。现在假设我们需要循环一个散列数组列表“$userList”,散列数组中包含“id”和“name”两个字段,示例见代码清单3-20,大家可以好好理解一下。
代码清单3-20
{* 注释:使用section标签循环 *}
{section name=user loop=$userList}
ID : {$userList[user].id}
NAME : {$userList[user].name}
{/section}
{* 注释:使用foreach标签循环 *}
{foreach $userList as $user}
ID : {$user.id}
NAME : {$user.name}
{/foreach}
从上面的代码中可以看出,Smarty 3.0的foreach用法已经和PHP的语法非常类似了,既容易理解又方便实用,推荐大家使用。另外,在Smarty中注释默认使用的是“{*...*}”标签,这个也需要大家了解一下。
由于篇幅限制,Smarty模板引擎的基本使用我们介绍到这里,关于其更多的信息请大家参考官方的文档并动手实践一下,毕竟Smarty模板也是使用PHP进行服务端开发的必不可少的一项技能。
3.6开发框架简介
前面大家已经学习了PHP模板引擎Smarty的用法,也简单了解了PHP的官方框架Zend Framework,接下来本书将给大家介绍一个基于Zend Framework和Smarty之上的强大的PHP开发框架,即Hush Framework。本书后面微博实例的服务端程序也将采用该框架进行开发。
在实际项目中,我们通常要先选择一个比较适合项目特点的框架,然后,在这个框架的基础上进行开发,这个过程我们通常称为“框架选型”。其实,在之前的3.4节中我们已经介绍和分析了四种目前比较主流的PHP框架,并选定了Zend Framework为本书实例的基础框架,我们通过长时间的整理归纳和项目积累,在Zend Framework和Smarty的基础上构建出了Hush Framework这个PHP的开发框架,个人还是非常推荐大家使用的,接下来我们从几个方面给大家介绍一下这个框架。
3.6.1框架的特点和优势
从某种程度上来说Hush Framework框架的特点也可以算做它的优势,所以我们主要给大家列举该框架的几个主要特点。
1. 开发效率高
Hush Framework基本沿用了Zend Framework严谨的编码规范和优秀的框架设计,形成了别具特色的MVC结构;此外,本框架目前已经被国内多个知名网络公司所采用,经过了多个实际项目的考验和提炼已经日渐成熟;另外,此框架相对比较适合国内程序员的思路,从而提高了开发效率。
2. 运行效率高
虽然Zend Framework的运行效率一直为大家所诟病,但是其中最重要的原因是类库实在太庞大了,因此Hush Framework只使用了其中几个核心类库,并对其中一些效率不够高的地方进行了精简和优化,比如URL的路由逻辑优化,DB类的用法简化等,极大限度地提高了整个框架的运行效率。
3. 可扩展性高
Hush Framework的高扩展性得益于Zend Framework优秀的类库设计,松耦合的设计方法让它能快速地适应不同项目的需求,这也是为什么我们会选择Hush Framework来作为本书实例的服务端底层框架的最主要原因之一。
当然,除了以上这些基础特点之外,Hush Framework还有很多其他的很棒的特性,比如我们可以用它的基础代码来快速开发一个常见的互联网应用,还可以用其自带的工作流模块来开发ERP系统等,但是由于本书实例仅用到框架中的最基础的MVC代码框架,所以我们后面的介绍也将围绕着Hush Framework的基本用法来给大家讲解,其中我们也会穿插一些PHP编程的要点,让大家更加了解如何在实际项目中使用PHP语言来编程。
3.6.2框架的基础目录结构
想要熟悉一个框架,最好的方式莫过于从它的代码目录结构入手,下面我们先来讲解一下Hush Framework的基础目录结构,让大家对这个框架有一个整体性的认识。下面便是对这个框架主要目录的一个对照,我建议大家使用svn工具到Hush Framework的官方网站(http://code.google.com/p/hush-framework/)上把代码下载到本地来进行比对阅读,这样才会达到比较好的学习效果。
目录说明3-1
hush-framework
- hush-app实例应用程序目录
- bin可执行文件目录
- dat临时存储文件
- doc主要文档目录
- etc配置文件目录
- lib主要逻辑目录
- Ihush
- AclACL 权限逻辑类库
- App
- Backend
- Page后台 Controller 逻辑
- Remote后台 Service 逻辑
- Frontend
- Page前台 Controller 逻辑
- BpmBpm 逻辑类库
- Dao
- AppsApps 库的 Module/Dao 类库
- CoreCore 库的 Module/Dao 类库
- tpl
- backend后台模板文件
- frontend前台模板文件
- web
- backend后台 DocumentRoot(站点目录)
- frontend前台 DocumentRoot(站点目录)
- hush-lib
- Hush
- AclAcl 权限类库
- AppApp Url Dispatcher
- Auth
- BpmBpm 类库
- CacheCache 类库
- Chart图像类库
- Crypt加密类(Rsa)
- Date
- Db数据库层(Module)类库
- Debug调试类库
- Document文档类库
- Examples一些例子(主要针对 Cli 程序)
- HtmlHtml 构建类库
- Http远程访问类库
- Json
- Mail邮件收发类库
- Message消息类库
- MongoMongodb 类库
- Page页面层(Controller)类库
- Process多进程类库
- Service服务层(Service)类库
- Session
- SocketSocket 类库
- Util工具类库
- View展示层(View)类库
- hush-pmsPHP Message Server
从上述目录结构说明中,我们可以看到Hush Framework的文件目录中,主要包含以下两大目录。
1. hush-app目录
该目录下的代码是Hush Framework给我们提供的框架实例程序,是一个比较完整的互联网应用实例,包括应用前端和管理后台两大部分,我们既可以把该实例当做一个代码示例库来学习和使用,也可以把它当做一个项目的基础架构进行二次开发;另外,之前也说过了,本书实例的服务端程序就是在本框架的基础之上开发的,因此从某种意义上来说和这里的实例程序是非常相似的,所以这里应该算是本书的重点之一了,接下来我们马上会对该实例中的一些主要用法和代码进行讲解。
特别注意一下hush-app下面的etc、lib、tpl和web四个目录,因为这四个目录分别是实例应用程序的配置目录、代码目录、模板目录和站点目录,下面我们来给大家详细介绍一下。
(1)配置目录(etc)
配置目录(etc)下面放置的都是应用的配置文件,其中比较重要的包括:全局配置文件(global.config.php)主要用于设置应用的总体配置,比如路径变量、类库位置等;数据库配置文件(database.mysql.php)用于设置数据库的服务器分布和分库分表策略等;前后台配置文件(frontend.config.php和backend.config.php)分别用于配置前后台的特殊参数。
(2)代码目录(lib)
本目录是主要的公用类库和逻辑代码目录,现将其中比较重要模块的代码分目录列举如下:权限控制模块(Acl目录)主要用于前后台的RBAC权限控制;控制器模块(App目录)用于各个页面的逻辑控制,也就是MVC中的Controller部分;工作流模块(Bpm目录)用于实例后台中工作流部分的逻辑控制;可执行程序模块(Cli目录)下面都是项目可执行程序的逻辑代码,另外我们需要知道的是hush-app/bin目录下面就是可执行程序的入口;数据操作模块(Dao目录)大家应该都非常熟悉了,这里保存的是和数据库操作相关的所有逻辑,也就是MVC中的Model部分。
(3)模板目录(tpl)
此目录下还分为前台模板目录(frontend目录)和后台模板目录(backend目录),分别用于存储应用实例前后台的Smarty模板,这也就是MVC中的View部分了,这里的模板和前面所提到过的“控制器模块”中的各个不同控制器的动作逻辑(Action)相对应。
(4)站点目录(web)
这里面放的都是一些静态文件或者独立的PHP代码等,此目录也分为前台站点目录(frontend目录)和后台站点目录(backend目录),另外这两个目录也是HTTP服务器的站点配置(DocumentRoot)所需要指定到的目录。
2. hush-lib目录
此目录保存的是Hush Framework的源代码,我们可以看到这里的代码目录和Zend Framework的结构非常一致,也就是把每个独立的模块代码都放在各自的目录下并尽量互不关联,这也比较符合“松耦合”的设计原则,既便于理解又便于阅读,是一个比较值得提倡的代码封装方法。
至此,已经给大家介绍了Hush Framework框架和应用(hush-app)中的重要代码目录,这是学习如何使用Hush Framework进行开发的重要一步,希望大家能好好消化一下以上内容。至于框架类库(hush-lib)源码中的模块和目录介绍,由于篇幅原因这里就暂时不做介绍了,有兴趣的读者可以访问Hush Framework在Google Code的官方站点,查找更多信息。
3.6.3框架MVC思路讲解
首先,Hush Framework是一个标准的MVC框架,MVC的概念大家应该都耳熟能详了,由于关系到本书重要的服务端底层框架的学习,我们还是不得不老调重弹。MVC是模型(Model)、视图(View)和控制器(Controller)的缩写,是目前业内最主流且应用最广泛的软件设计模式之一,MVC具备以下主要优点。
1. 低耦合性
MVC最大的好处之一就是大大降低了程序的耦合性,特别是把视图层和业务逻辑分开之后,让整个应用灵活了很多;当业务逻辑有变化时,我们只需修改模型层和控制器的代码,而无须修改负责应用外观的视图层。
2. 高重用性
MVC的高重用性主要体现在两方面。一方面是视图层的重用性,因为在实际的项目中,我们经常需要修改应用外观来满足需求,由于业务逻辑已经分离出来,所以我们不需要更改逻辑就可以调整应用界面,满足了软件高重用性的要求。另一方面,业务逻辑被分为模型层和控制器层之后,既保证了核心数据结构的稳定性,也增强了业务逻辑控制器的灵活性,这样我们就可以很好地重用核心数据结构来实现多种多样的业务逻辑。
3. 可维护性
MVC设计模式把模型、视图和控制器分开,实际上也把设计者、程序员和UI设计人员的职能做了分离。这种分工方式不仅可以让应用的架构看起来更清晰,还便于软件维护时团队之间的共同协作,这对中大型软件应用程序的代码维护工作将起到很重要的作用。
之前讨论的是MVC设计模式中比较通用的概念和知识,然而对于不同的框架来说,具体的实现方式却有各自不同的特色,接下来我们就来学习Hush Framework中的MVC设计思路。为了让大家理解起来更容易,我们把Hush Framework处理客户端请求的完整过程通过图形的方式展示出来,如图3-16所示。
从图3-16中我们可以很清楚地看到客户端请求的整个处理过程,在一个标准的基于HTTP协议的互联网应用环境中,用户每次操作都会致使浏览器向HTTP服务器发送HTTP请求,当服务器接收到请求之后,就会调用框架程序来处理,此时Hush Framework就会接管接下来的工作。具体的处理流程一般分为以下几个步骤。
步骤1:首先,Hush Framework的请求分发器(Dispatcher)会分析客户端发送过来的HTTP请求所包含的信息,并根据请求的URL地址来指定使用相应的控制器(Controller)来处理该请求。
步骤2:接着,被指定的控制器会选择合适的模型类(Model)用于持久层数据的获取和存储,并负责处理该请求的业务逻辑。此外,我们可以看到Hush Framework的模型层是基于Zend Framework的模型层的。当然,在逻辑处理完成后,控制器还会调用视图层(View)来组合出最终的HTML代码。
图3-16Hush Framework系统处理分析图
步骤3:最后,服务器会把Hush Framework的处理结果通过HTTP协议返回给客户端程序来进行后续的处理。
通过分析我们会发现,实际上Hush Framework的MVC设计和实现的思路还是比较主流的,和大部分网络应用的MVC框架的思路也比较相似,不过其中还是有不少独特的亮点。比如Hush Framework的分发器(Dispatcher)就使用了独有的快速分发逻辑,大大提高了程序的运行效率;另外,不仅支持常见的URL路由分发模式,还支持通过“路由映射文件”这种更直观的方式来进行更精细的配置,映射文件代码如配置清单3-3所示。
配置清单3-3etc/app.mapping.ini
; URL mappings
;
; Used by Hush_App_Dispatcher class
; e.g /url/path = PageClassName::ActionName
;
/= DebugServer::indexAction
/debug/*= DebugServer::*
另外,Hush Framework的模型层使用了Zend Framework作为底层框架,沿用了其完善的DB模型层封装和方法的设计,并使用自己独特的思路封装成Hush Framework的DAO基类,让建立在基类之上的持久层操作更加简便、高效。在接下来的3.6.4节中,我们将通过实例来讲解Hush Framework持久层的用法。
3.6.4框架MVC实例分析
通过前面两节的学习,大家应该对Hush Framework框架的理论基础有了一定的认识,但是理论还是需要通过实践来证明,要真正学会如何运用该框架进行开发,光靠理论知识是远远不够的,所以在本节中我们将会围绕着框架实例中与MVC分层开发相关的实例代码为大家做进一步的讲解。下面我们会使用框架实例中的“后台登录”这个界面的完整逻辑,来给大家讲解一下在Hush Framework中我们是如何使用MVC的分层思路来进行编程的,我们先来看一下这个界面的截图,如图3-17所示。
图 3-17框架实例后台登录界面
以上是在浏览器中打开框架实例的后台站点时看到的界面,也就是后台的登录界面。这里我们可以看到浏览器中的地址是“http://hf-be/auth/”,按照前面所介绍的Hush Framework的应用目录中所提及的,对应的控制器类应位于lib/Ihush/App/Backend/Page/AuthPage.php文件中。至此,既然已经找到分析框架MVC用法的“突破口”,那么我们就从这里开始分析吧。
我们先来看看AuthPage.php文件中的AuthPage类,此类继承自Ihush_App_Backend_Page类(即整个实例应用的后台控制器基类),其中包含多个以Action为后缀的方法,分别对应于“后台登录”界面的几个逻辑,整个类的写法都是面向对象的,大家可以对照前面3.1.4节的内容理解一下,关于其中涉及的MVC分层思路,下面我们会把这三层的相关代码提取出来,分别给大家讲解一下。
1. 控制器(Controller)
控制器简单来说就是页面的逻辑,在Hush Framework中我们通常使用Page(页面)来表示通常意义的Controller,因为对于网络应用来说Page比Controller更好理解;此外,我们还需要知道Hush Framework使用的是REST格式的URL路径结构,自域名之后的URL路径第一个表示的是控制器的名称,第二个则是控制器的动作,也就是我们通常所称的Action,比如登录界面的路径为“/auth/”,其对应的逻辑就可以在AuthPage.php文件中AuthPage类的indexAction方法中找到,这里需要注意的是当前路径如果为空,我们则会用index来代替。代码清单3-21就是AuthPage类的完整实现,大家可以参考注释来阅读代码。
代码清单3-21
/**
* @package Ihush_App_Backend
*/
class AuthPage extends Ihush_App_Backend_Page
{
public function indexAction ()
{
// TODO : 默认使用index.tpl作为Action的模板
}
public function loginAction ()
{
// 用户名/密码/验证码均不能为空
if (!$this->param('username')
!$this->param('password')
!$this->param('securitycode')) {
$this->addError('login.notempty');
}
// 验证码必须是正确的
elseif (strcasecmp($this->param('securitycode'),$this->session('securitycode'))) {
$this->addError('common.scodeerr');
}
// 通过参数验证
if ($this->noError()) {
// 使用DAO类的验证方法
$aclUserDao = $this->dao->load('Core_User');
$admin = $aclUserDao->authenticate($this->param('username'),
$this->param('password'));
// 登录失败(找不到用户)
if (!$admin) {
$this->addError('login.nouser');
}
// 登录失败
elseif (is_int($admin)) {
$this->addError('login.failed');
}
// 登录成功
else {
// 是否是超级用户
$admin['sa'] = strcasecmp($admin['name'], $this->sa) ? false : true;
// 保存登录用户信息到会话
$this->session('admin', $admin);
// 跳转至首页
$this->forward($this->root);
}
}
// 登录失败则显示登录界面
$this->render('auth/index.tpl');
}
public function logoutAction ()
{
if ($this->session('admin')) {
// 用户登出,清除用户会话信息
$this->session('admin', '');
}
$this->forward($this->root);
}
}
从以上的代码中我们可以看出,在控制器类AuthPage中有三个Action方法,分别是indexAction、loginAction和logoutAction,这些方法对应的三个功能分别是“展示登录界面”、“用户登录逻辑”和“用户登出逻辑”,以上功能的基本逻辑和涉及的PHP语法这里就不做解释了,接下来我们会给大家分析一下这几个Action方法中比较重要的知识点,学习并理解这些知识点后,有助于我们分析AuthPage控制器类的代码。
(1)使用render方法展示模板
首先我们需要知道的是在Hush Framework中使用模板有两种方式:首先是默认方式,此种方式是按照“模板根目录/Controller名/Action名.tpl”这样的规则来放置的,因此对于indexAction来说我们可以根据这个规则分析出其对应模板是tpl/backend/template/auth目录下的index.tpl;另外一种方式是通过render手动设置模板,这也正是在loginAction中最后的那行代码所做的事情,可参考代码清单3-22中的写法。
代码清单3-22
// 登录失败则显示登录界面
$this->render('auth/index.tpl');
这行代码的逻辑其实非常容易理解,就如同注释中描述的一样,当用户登录失败后,程序还是应该展示出登录页面给用户重新填写登录名和密码。
(2)使用param方法获取URL参数
param方法是开发中最常用的方法之一,该方法一般用于获得GET或者POST过来的URL参数;另外,如果这个函数带两个参数,则是设置对应URL参数的值。示例代码如代码清单3-23所示。
代码清单3-23
// 获取GET或者POST过来的username参数值
$username = $this->param('username');
// 设置username参数值为james
$this->param('username', 'james');
(3)使用addError方法处理错误信息
大家都知道,在网页表单提交之后,我们会先做一些字段的判断,比如用户名和密码是否为空等,如果这些验证没有通过,就要给页面传递一些错误信息,而addError方法就是做这个事情的,比如前面实例中的代码“$this->addError('login.notempty');”就是用来显示错误信息的,另外对应的错误信息“login.notempty”我们可以在“etc/backend.errors.ini”文件中找到。
(4)使用load方法来加载DAO类
框架已经帮助我们把Controller层中如何使用Model层的方法封装好了,那就是这里所说的load方法,我们可以使用如下代码获取任意一个DAO类,如代码清单3-24所示。
代码清单3-24
$aclUserDao = $this->dao->load('Core_User');
$admin = $aclUserDao->authenticate($this->param('username'), $this->param('password'));
以上代码取自于loginAction中的部分逻辑:首先,初始化了Core_User的DAO类供我们使用;然后,使用该类中的authenticate方法判断用户是否登录成功,这个地方的逻辑我们会在下面的模型层中做详细分析。
小贴士:前面提到的DAO是数据访问对象(Data Access Objects)的缩写,该对象常被用于进行数据库层面的各种数据操作,后面我们会经常提到。
(5)使用forward进行页面跳转
在一个互联网应用中,页面跳转是再常见不过的事情了,这里我们也能找到相应的示例代码,也就是登录成功之后跳转到首页的逻辑,如代码清单3-25所示。
代码清单3-25
$this->session('admin', $admin);
$this->forward($this->root);
(6)使用session函数操控会话
会话的概念相信有些网络开发基础的朋友都应该清楚,因为HTTP是无状态的,所以我们一般会使用会话(Session)来保存用户相关的信息,在loginAction和logoutAction中我们都可以看到相关的代码,如代码清单3-26所示。
代码清单3-26
// 保存登录用户信息到Session
$this->session('admin', $admin);
// 用户登出,清除用户会话信息
$this->session('admin', '');
2. 视图层(View)
视图层主要负责的是对应控制器逻辑的展示,一般来说是由HTML语法和Smarty变量构成的。根据前面介绍的关于Hush Framework中的两种模板使用方式,我们可以“顺藤摸瓜”找到indexAction对应的模板,也就是tpl/backend/template/auth中的index.tpl模板文件,如代码清单3-27所示。
代码清单3-27
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>用户登录</title>
<link href="{$_root}css/main.css" rel="stylesheet" type="text/css" />
<link href="{$_root}css/login.css" rel="stylesheet" type="text/css" />
{literal}
<script type="text/javascript">
if(self!=top){top.location=self.location;}
</script>
{/literal}
</head>
<body>
<div class="login-body">
<div class="login-con">
<h1><img src="{$_root}img/logo_s.gif" /><span>后台管理系统</span></h1>
<div class="login">
{include file="frame/error.tpl"}
<form action="{$_root}auth/login" method="post">
<input type="hidden" name="go" value=""/>
<input type="hidden" name="do" value="login"/>
<ul>
<li>
<label>用户名:</label>
<input type="text" class="text" name="username"/>
</li>
<li>
<label>密码:</label>
<input type="password" class="text" name="password"/>
</li>
<li>
<label>验证码:</label>
<input type="text" class="text" style="width: 50px;margin-right:5px;
text-transform: uppercase;" id="securitycode"
name="securitycode" autocomplete="off"/>
<img id="securityimg" src="{$_root}app/scode/image.php"
alt="看不清?单击更换" align="absmiddle"
style="cursor:pointer" onClick="this.src=this.src+'?'" />
</li>
<li>
<input type="submit" onclick="this.form.submit();"
class="submit" value="登录" name="sm1"/>
</li>
</ul>
</form>
</div>
</div>
</div>
</body>
</html>
以上代码大部分是比较简单的HTML语法,穿插了一些Smarty的变量,比如“{$_root}”就是设置好的全局的Smarty的变量,代表项目URL的根路径,默认是“/”。另外,在该模板里我们还看到了登录表单的代码“<form action="{$_root}auth/login" method="post">”,这里我们可以发现该登录表单将会被提交至“/auth/login”路径,其对应逻辑就在前面我们分析过的控制层中AuthPage类的loginAction方法里。
3. 模型层(Model)
模型层是MVC三层中最接近数据库的一层,里面放的是数据操作的逻辑,也就是说我们常说的CRUD操作,这部分也是我们需要重点了解的。在前面的登录界面的示例中,我们了解到loginAction中使用到了DAO类Core_User里面的authenticate方法,下面我们截取Core_User里面的相关代码给大家讲解一下,见代码清单3-28。
小贴士:CRUD操作是添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)的缩写,也就是我们常说的“增删查改”方法,这几个操作基本包含了数据操作类DAO绝大部分的使用方式,后面我们也会经常提到。
代码清单3-28
/**
* @package Ihush_Dao_Core
*/
class Core_User extends Ihush_Dao_Core
{
/**
* 设置表名
* @static
*/
const TABLE_NAME = 'user';
/**
* 设置主键
* @static
*/
const TABLE_PRIM = 'id';
/**
* Initialize
*/
public function __init ()
{
$this->t1 = self::TABLE_NAME;
$this->t2 = Core_Role::TABLE_NAME;
$this->rsh = Core_UserRole::TABLE_NAME;
// 绑定常用CRUD操作
$this->_bindTable($this->t1);
}
/**
* 登录验证方法
* @uses Used by user login process
* @param string $user 用户名
* @param string $pass 密码
* @return bool or array
*/
public function authenticate ($user, $pass)
{
$sql = $this->select()
->from($this->t1, "*")
->where("name = ?", $user);
$user = $this->dbr()->fetchRow($sql);
if (!$user['id'] !$user['pass']) return false;
if (strcmp($user['pass'], Hush_Util::md5($pass))) return $user['id'];
$sql = $this->select()
->from($this->t2, "*")
->join($this->rsh, "{$this->t2}.id = {$this->rsh}.role_id", null)
->where("{$this->rsh}.user_id = ?", $user['id']);
$roles = $this->dbr()->fetchAll($sql);
if (!sizeof($roles)) return false;
foreach ($roles as $role) {
$user['role'][] = $role['id'];
$user['priv'][] = $role['alias'];
}
return $user;
}
...
}
接下来,我们来分析一下Core_User类中使用到的几个功能要点,并以此为实例给大家介绍一下Hush Framework中模型层的核心用法,也就是框架DAO基类中已经封装好的数据库常见操作的编码和使用。
(1)DAO类的初始化
在Hush Framework中使用DAO类,首先需要配置一个和数据表相对应的DAO类,这个过程我们通常称为DAO类的初始化。其实配置一个DAO类是非常方便的,因为框架DAO基类已经帮我们封装好了绝大部分DAO类所需要的逻辑和方法,所以初始化起来非常简单。代码清单3-29就是一个最简单的DAO类的范例模板。
代码清单3-29
// DbName为数据库名
// TableName为数据表名
class DbName_TableName extends Dao_DbName {
// 配置表名
const TABLE_NAME = ' TableName ';
// 配置主键名
const TABLE_PRIM = 'PrimaryKey';
// 初始化操作
public function __init () {
// 绑定常用的CRUD操作
$this->_bindTable(TABLE_NAME);
}
}
我们可以看到,区区几行代码就已经把一个DAO类写好了。以上代码中的DbName表示数据库名,TableName表示表名,PrimaryKey则表示主键名,__init是初始化方法,__bindTable主要用于绑定CRUD方法,也就是说,初始化之后我们就可以直接使用这个DAO类来进行“增删查改”操作了。
(2)DAO类中的查询方法
查询应该是数据库最主要的用途之一,这里我们会重点讲解在Hush Framework的DAO类中使用查询的要点。从前面提到的Core_User数据操作类中的authenticate方法中我们可以看到在DAO类中经常使用到的查询(select)方法的使用范例,包括普通查询和表关联查询,示例见代码清单3-30。
代码清单3-30
…
// 普通查询
$sql = $this->select()
->from($this->t1, "*")
->where("name = ?", $user);
$user = $this->dbr()->fetchRow($sql);
…
// 表关联查询
$sql = $this->select()
->from($this->t2, "*")
->join($this->rsh, "{$this->t2}.id = {$this->rsh}.role_id", null)
->where("{$this->rsh}.user_id = ?", $user['id']);
$roles = $this->dbr()->fetchAll($sql);
在Hush Framework中,我们可以使用和Zend Framework类似的方式来“拼装”数据库SQL查询语句,其代码语法还是比较容易理解的,我们可以把其中的select方法、from方法、where方法以及join方法分别理解为SQL语句中的SELECT、FROM、WHERE以及JOIN这几个关键词,理解起来会更加清晰。当然除了以上这几个方法之外,框架底层还提供了LIMIT、GROUP BY和ORDER BY等常用SQL语句的对应方法。比如代码清单3-31中例举的就是一些相对复杂的SQL语句所对应的PHP代码的写法。
代码清单3-31
// 对应标准SQL:
// SELECT COUNT(id) AS count_id
// FROM foo
// GROUP BY bar, baz
// HAVING count_id > "1"
$select = $db->select()
->from('foo', 'COUNT(id) AS count_id')
->group('bar, baz')
->having('count_id > ?', 1);
// 对应标准SQL:
// SELECT * FROM round_table
// ORDER BY noble_title DESC, first_name ASC
$select = $db->select();
->from('round_table', '*')
->order('noble_title DESC')
->order('first_name');
当然,我们需要理解Hush Framework的这种使用方法来替代SQL语句的做法,因为对于不同的数据库,查询语句区别是比较大的,如果没有一个很好的通用SQL语句的引擎很难做到良好的通用性,然而这却恰恰是本框架的优势所在;正是因为有底层的Zend_Db来提供强大的基础,才能让Hush Framework的模型层运转得更加得心应手。为了说明这点,我们以代码清单3-32为例,可以看到同样的DAO查询语句在不同的数据库中被解释成了不同的SQL;这样我们就不需要关心应用所使用的数据库类型,简便地写出通用型的代码,大大提高了模型层代码的重用性。
代码清单3-32
// 在 MySQL/PostgreSQL/SQLite 中,对应 SQL 如下:
// SELECT * FROM foo
// ORDER BY id ASC
// LIMIT 10
//
// 在 Microsoft SQL 中,对应 SQL 如下:
// SELECT TOP 10 * FROM FOO
// ORDER BY id ASC
$select = $db->select()
->from('foo', '*')
->order('id')
->limit(10);
此外,我们还需要注意一点,Hush Framework中的数据库类都是支持读写分离的,因此这里我们使用“dbr()->fetchRow(...)”方法(dbr是只读数据库db-read的缩写)来表示从“读库”中获取内容,一般来说数据查询操作中的绝大部分情况都会使用此方法;当然与之相对的,如果我们要写入数据的话,则应该使用dbw方法来操作“写库”,比如“dbw()->delete(...)”就是在“写库”中删除信息的写法。
(3)DAO类中的CRUD方法
前面我们已经介绍了DAO类中查询操作的用法,以及Hush Framework中对于数据库读写分离用法的使用要点,对于查询操作来说我们应该使用读库,但是对于CRUD中的其他几种操作来说就应该使用写库了,也就是使用“dbw()”方法进行调用,下面我们把除了select之外的几种方法给大家介绍一下。
create方法:此方法用于创建数据,只要传入的是包含数据的散列数组,我们就可以在数据表中添加一条记录。这里需要注意的是,我们在CRUD方法中传递的数据格式经常是类似“array(key1=>value1,key2=>value2...)”格式的数组,key是键名,对应的是数据表的字段名,而value则是数据,代表的是对应键名的数据。
exist方法:此方法用于来检测数据是否存在,一般来说我们可以传入主键值进行判断,当然如果我们需要根据其他字段的值来进行判断也是可以的,只需要在第二个参数传入对应字段名即可。
read方法:此方法也是和主键相关的,用于读取与对应主键相关的数据行。因为此方法不需要组装SQL,使用起来比select方法简单许多,所以在获取与主键有关的数据行的情况下我们常用它来替代select方法。如果不使用主键,我们也可以在第二个参数传入对应字段名。
update方法:此方法用于更新数据行,既可直接传入带主键的数组进行更新(此种情况将会按照主键值更新对应数据行)。当然,我们也可以在第二个参数传入where语句进行更新。
delete方法:此方法用于删除数据行,与前面的update方法类似,我们既可直接传入主键值进行删除(此种情况将会按照主键值删除对应行)。当然,我们也可以在第二个参数传入对应字段名。
replace方法:此方法用于替换数据行,在MySQL数据库中比较常用,一般我们替换的数据行也是和主键有关系的,或者是组合型主键。
到这里,我们已经把整个代码示例“登录界面”的逻辑介绍完了,同时也把Hush Framework中如何使用MVC的思路来进行编程的基本方法讲了一遍,现在大家应该对如何使用Hush Framework来进行开发心里有数了吧。由于Hush Framework是完全面向对象的,这里大家还可以学到许多PHP语言中的面向对象编程的技巧。当然最好的学习方法就是动手,我建议大家把框架的实例代码架设起来,然后直接动手边调试边学习,以达到“学以致用”的最佳效果。另外,关于如何获取Hush Framework框架源码以及如何部署源码实例的内容,我们会在附录A中给大家做详细介绍。
3.7小结
本章中我们比较全面地介绍了使用PHP语言进行开发的几个方面。从最基本的PHP语法、语言特点,到PHP面向对象编程思路、常用开发环境的介绍,再到PHP配套开发组件(Apache和MySQL等)和主流开发框架的分析和使用;如果大家能够把本章所介绍的这些知识全部掌握,那么可以说我们就已经具备了使用PHP语言进行互联网项目开发的基本条件,接下来还需要进一步学习的就是实战经验了。
- Android和PHP开发最佳实践
- Android和PHP开发最佳实践 第1章
- Android和PHP开发最佳实践 第2章
- Android和PHP开发最佳实践 第3章
在线试读:
系列图书推荐
Kotlin移动应用开发
- ¥99.00
- ¥49.50
- Kotlin移动应用开发
[套装书]Flutter实战+Flutter技术入门与实战 第2版(2册)
- ¥188.00
- ¥122.20
- [套装书]Flutter实战+Flu..
Flutter实战
- ¥99.00
- ¥49.50
- Flutter实战
[套装书]Flutter实战+React Native 精解与实战(2册)
- ¥178.00
- ¥115.70
- [套装书]Flutter实战+Rea..
[套装书]Flutter实战+Flutter技术入门与实战 第2版+Android插件化开发指南(3册)
- ¥267.00
- ¥173.55
- [套装书]Flutter实战+Flu..
同类热销商品
Java编程思想(第4版)
- ¥108.00
- ¥75.60
- Java编程思想(第4版)
GPU高性能编程CUDA实战[图书]
- ¥39.00
- ¥39.00
- GPU高性能编程CUDA实战[图..
算法导论(原书第3版)
- ¥128.00
- ¥89.60
- 算法导论(原书第3版)
Linux高性能服务器编程[按需印刷]
- ¥69.00
- ¥69.00
- Linux高性能服务器编程[按..
计算机网络:自顶向下方法(原书第7版)
- ¥89.00
- ¥62.30
- 计算机网络:自顶向下方法..