• 1. Android BasicBy 王家林
  • 2. Android 未插完的花盆…
  • 3. 软件霸业的两股力量1,Application Framework 2,Hardware Abstraction Layer
  • 4. Android的思维1,做框架(Application Framwork和HAL)送给全世界; 2,强龙与地头蛇的模式 强龙:Android支撑Google在手机、家电产业上的强龙地位; 地头蛇:应用程序开发者和硬件厂商 AP开发者:编写应用软件; 硬件厂商:开发手机硬件;
  • 5. Android的思维的实现方式1,Google开发手机平台软件系统; 2,Android中包含两个框架: Java应用层框架:支撑应用程序的开发; HAL(Hardware Abstraction Layer)驱动框架,用来支撑硬件组件的驱动程序(Driver);
  • 6. Android的胜利者之硬件厂商1,HuaWei、HTC、Motorola、Samsung等 2,Android中的Linux内核是遵循GPL协议的,写在内核中的程序就必须开源,对硬件厂商而言,开源的驱动软件可能会让硬件的创新功能曝光,容易被仿制,难以产生差异化和利润。 3,如何实现“没钱就改版,改版就有钱。”?
  • 7. Google为对硬件厂商的支持1,建立HAL层,为驱动软件和硬件的变动带来自由; 2,让驱动程序成Linux的内核空间(Kernal Space)移到HAL框架区域,而框架区域属于用户空间(User Space); 3,驱动程序在HAL框架的用户空间里,才去ASL自由软件协议; 4,采用ASL协议,驱动程序不必提供源代码。
  • 8. Android的胜利者之云服务提供商1,从架构模式上讲云服务提供商和硬件提供商是一致的; 2,云服务放在C/C++ Library中; 3,云服务放在Application Framework中; 4,应用程序通过Android使用服务; 5,两种云服务的区别;
  • 9. Android的胜利者之应用程序开发者1,Android Market; 2,Application Framework; 3,Java语言; 4,Android应用开发大赛;
  • 10. Android的核心力量源泉1,Application Framework和HAL; 2,IoC;
  • 11. Android应用框架的特点目的:让应用软件工程师派生具体子类 应用框架中的函数常需要调用应用程序中的函数 既定的类别之间的关系,预设对象之间的交互关系和行为 拥有预设的函数实现(Default Behavior),Andorid应用开发工程师可以可以根据需要在子类中修改默认行为。
  • 12. IoC的定义和两种方式IoC:框架拥有控制权而主动调用应用程序的情形同称为IoC IoC的两种方式:继承接口、依赖注入(Dependency Inject)。 DI:此时的框架是一个Container,掌握对象的Lifecycle和Structure。
  • 13. 创建框架的步骤抽象:抽出共同之现象 观察 分别异同点 抽取相同点 派生或者Contain Interface
  • 14. 面向对象语言并不是面向对象的 面向对象语言并不是面向对象的,因为面向对象编程的三个核心原则如下: 1,基于消息传递:系统会对消息进行透明的处理,你不需要知道消息从哪里来。 2,对象之间保持分离:一个对象任何行为应该不影响另外一个对象,但对于Java程序不是这样的,因为如果两个程序同时放在JVM中运行,其中一个导致虚拟机停机,另外一个也会停掉。 3,多态;
  • 15. 面向对象的核心控制力1,IoC(Inversion of Control)的核心观念:Interface和抽象类拥有控制权; 2,抽象的的步骤: 分辨:把稳定和易变的部分分离开; 封藏:把差异化的易变的部分放在子类; 抽象:把相同点抽离出来; 3,接口和抽象类拥有控制权,呼叫子类。
  • 16. 面向对象思想的商业价值1,老子说了“人皆知有用之用,而莫之无用之用”老子还说了“有之以为利,无之以为用” 2,万里长城的思考; 3,接口和实现类的分层; 4,反向控制;
  • 17. 面向对象思想是如何应对变化的1,抽象:抽象出变化中不变的部分; 2,多态:指定协议,例如对于所有的对象都拥有打印自己的方法,这样软件工程师不用记住类名了,因为多态的存在,所有的实现者都必须实现抽象方法。 3,框架:框架控制应用程序;
  • 18. 面向对象思想是如何应对复杂的1,使用框架技术:由框架控制应用程序,并实现一个默认的功能; 2,消息传递:对象只负责接收消息,而不用关系消息是怎么传递的; 3,分离:强内聚、弱耦合;
  • 19. Google、微软等如何应用面向对象的思想和技术创造巨大的商业价值1,框架的发展史: 1980年代初期---Smalltalk-80的MVC Framework 1980年代中期---Macintosh电脑的MacApp Framework 1990年代初期---Visual C++的MFC Framewor 1990年代中期---IBM的San Francisco Framework 2000年-----------Microsoft的.Net Framework 2007年-----------Google的Android框架 2,使用IoC打造框架,使框架拥有主控权:控制子类、控制流程、默认实现、屏蔽复杂
  • 20. 深度剖析Java、Dalvik VM、C/C++的运行机制与流程 1,Dalvik的实例化 2,关于ClassLoader 3,Dalvik VM处理Java与C/C++本质 4,Java、Dalvik VM、C/C++的运行机制与流程
  • 21. Java中分配线程调用C/C++函数 1,Java中分配线程调用C/C++函数的应用场景 2,Java中分配线程调用C/C++函数设计思考 3,Java中分配线程调用C/C++函数流程 4,Java中分配线程调用C/C++函数代码实战
  • 22. C/C++本地代码通过分配线程调用Java函数 1,JC/C++本地代码通过分配线程调用Java函数的应用场景 2,C/C++本地代码通过分配线程调用Java函数设计思考 3,C/C++本地代码通过分配线程调用Java函数流程 4,C/C++本地代码通过分配线程调用Java函数代码实战
  • 23. 什么是3G<一>3G,全称为3rd Generation,中文含义就是指第三代数字通信。 所谓3G,是指将无线通信与国际互联网等多媒体通信结合的新一代移动通信系统。 3G只是一种通信技术标准,符合这个标准的技术有WCDMA、CDMA2000、TD-SCDMA三种无线接口标准。中国联通使用的是WCDMA(世界上大部分3G网络都采用的是该标准) ;中国电信使用的是CDMA2000 (日、韩和北美使用);中国移动使用的是具有自主知识产权的TD-SCDMA(只有中国才使用) 。相对第一代模拟制式手机(1G)和第二代GSM、CDMA等数字手机(2G),3G手机能处理图像、音乐、视频等多种媒体形式,提供包括网页浏览、电话会议、电子商务等多种信息服务。 3G网络与前两代的主要区别是整合了互联网并且数据传输速度有很大的提升。
  • 24. 什么是3G<二>目前中国正在建设3G网络,大城市的3G网络基本上已经铺设好,像北京铺设好了超过2000个基站,深圳铺设了1000多个基站。但是要全民普及到3G 手机尚且需要几年时间。现在,大家使用的手机大多还是2.5G手机,符合2.5G网络的接口标准有CDMA2000 1X和GPRS,中国联通使用的是CDMA2000 1X标准,中国移动使用的是GPRS标准。目前,我们可以把2.5G移动通信技术看作是2G迈向3G的衔接性技术,在2.5G网络下出现了如WAP、蓝牙(Bluetoot) 等技术。对于2.5G网络,我们应该也要有所了解,因为以后大家到企业中有可能会面对2.5G网络下的应用,如:wap项目。
  • 25. 什么是AndroidAndroid是Google在2007年11月5日推出的开源手机操作系统。目前Android在中国的发展是非常火的,为了节省研发费用,国内很多的手机厂商和移动运营商已经开始使用上android操作系统了,其中包括中国移动,中国联通,中国电信,华为,联想和一些山寨手机厂商。 另外android应用的范围不仅仅在手机,还被应用在汽车、平板电脑、和智能上网设备上。 因为目前大家使用的手机大多属于傻瓜手机,也就是打打电话、发发短信。从今天开始,大家对智能手机要有个重新的认识,智能手机除了可以打电话发短信之外还能完成电脑所能完成一切,所以大家需要把智能手机看作是一台电脑。
  • 26. Android的升级Android系统今后将继续每半年一次的升级步伐,分别定在每年的夏天和年终。每代Android系统都 将以甜点命名.比如: 1.5版叫做Cupcake(纸杯蛋糕) 1.6版为Donut(甜甜圈) 2.1版的Éclair(闪电泡芙,一种法式奶油夹心甜点),此版本曾被叫做Flan“水果馅饼”,之后是 2.2版的Froyo(冻酸奶) 2.3版的Gingerbread(姜饼).添加了sip通话的支持,在省电上下了功夫,做成了黑色主题 3.0版的Honeycomb(蜂巢)主要是针对平板电脑,为平板和手机的整合做准备,有了对设备的判断开关 4.0版的Ice cream SandWich(简称ics,冰激凌三明治) 统一版本,电视手机平板融合 以CDEFG字头顺序排列.Donut将把社交网络功能作为升级重点,在"手机的各种体验中"都增加社交 网络元素[7]. 对手机业界的影响 已经与HTC、NTT DoCoMo、KDDI、Motorola等世界通信领域34家公司于免费提供达成一致。今后 对于移动通信的影响势必会进一步体现出来。但是如此广泛公司的同盟是否能够有效运作及以持久值 得拭目以待。
  • 27. 智能手机软件平台智能手机软件平台有: Symbian Windows Mobile RIM BlackBerry Android iPhone Java/J2ME
  • 28. Android的未来android和自身-消费市场的开源挑战 Android的最大的挑战是开源,android可能永远是开源项目,为了成功必须要销售数百万台,它 不是第一个开源手机,但是google领衔移动市场的开始. 开源是双刃剑,有着大量的人才和公司资源.但另一方面,代码不集中,就像分裂的linux一样. 但是google也有他自己给予开发商的约束:cts测试 这是google程序员自己编写的测试case.其目的就是不让我们的开发商对代码的结构做太大变化的修改,如果说测试case不能通过,那么我们的这个操作系统就无法得到google的认证. 授权android android以两种不同的开源许可发布.linux内核是GPL发布,开源os需要该授权.android平台 (不含内核)由apache发布.两种发布模式都是开源为主,不同是后者面向商业用途.
  • 29. Android软件堆栈
  • 30. Dalvik vm 和 jvm的比较区别 jvm dalvik vm 优点 编译后文件格式 .java->.class->.jar .java->.class->.dex->.odex 在编译时优化代码,而不是 在运行时,将多个文件整合 成一个,整体减少文件个数 i/o操作,提高类查询的速 度,常量池的引入 字节码格式 零字节地址格式 二/三地址的混合形式 执行的效率要高些,成操作 需要更多的load/store指 令(指令分配次数和内存访 问次数),二/三地址占内 存多些,但操作更少,访问 内存执行数度是一个瓶颈 基于的架构 基于栈的架构 基于寄存器的架构
  • 31. Android应用框架的特点目的:让应用软件工程师派生具体子类 应用框架中的函数常需要调用应用程序中的函数 既定的类别之间的关系,预设对象之间的交互关系和行为 拥有预设的函数实现(Default Behavior),Andorid应用开发工程师可以可以根据需要在子类中修改默认行为。
  • 32. Android应用程序与使用者的交互关系图
  • 33. 如何安装 Android SDK 和Eclipse 插件<一>所需开发环境:JDK 5 或 JDK 6 (仅有JRE不够) 、 Eclipse 3.4 或以上版本 下载安装Android SDK:Android SDK包含了开发Android应用所依赖的jar文件、运行环境及相关工具。 下载地址: http://dl.google.com/android/android-sdk_r13-windows.zip 下载完SDK后,把.zip文件解压到你电脑上合适位置。使用解压包中的SDK Manager安装需要的组件,我们在这里安装的Android2.2.启动 Eclipse,选择window->preferences,在打开的视图左边点击android,在右边的SDK Location中选择Android SDK所在位置。
  • 34. 如何安装 Android SDK 和Eclipse 插件<二>安装 Eclipse 插件 (ADT) 启动 Eclipse,选择 Help > Install New Software,在出现的对话框里,点击Add按钮,在对话框的name一栏输入“ADT”, 然后点击Archive...,浏览和选择已经下载的ADT插件压缩文件。 点击 OK.。返回可用软件的视图,你会看到这个插件,然后选择Developer Tools (会选中下面的“Android Developer Tools”和 “Android Editors“),点击 Next,最后重启 Eclipse。
  • 35. 如何安装 Android SDK 和Eclipse 插件<三>安装 Eclipse 插件 (ADT) 方法一:启动 Eclipse,选择 Help > Install New Software,在出现的对话框里,点击Add按钮,在对话框的name一栏输入“ADT”, 然后点击Archive...,浏览和选择已经下载的ADT插件压缩文件。 点击 OK.。返回可用软件的视图,你会看到这个插件,然后选择Developer Tools (会选中下面的“Android Developer Tools”和 “Android Editors“),点击 Next,最后重启 Eclipse。 方法二: Help > Install New Software> click Add> Location输入 https://dl-ssl.google.com/android/eclipse/
  • 36. 开发第一个Android应用打开Eclipse,新建项目( 点击File NewProject), 在项目列表中展开Android目录, 选择Android Project:
  • 37. 开发第一个Android应用点击”finish”即可完成项目的创建,创建后的项目已经是一个可运行的Android应用,我们可以通过下面方式运行此应用: 点击工具栏上机器人形状的虚拟设备管理器(简称“AVD“),如下:
  • 38. 开发第一个Android应用在打开的虚拟设备管理器 中创建一个虚拟手机:
  • 39. 屏幕分辨率 屏幕分辨率 HVGA:320×480 QVGA:240x320 WQVGA400:240X400 WQVAG432:240X432 WVGA800: 480X800 WVGA854: 480X854 Android软件开发普遍支持的机型:HTC G1,G2,G3,G4 和 Moto Droid 模拟器屏幕切换Ctrl+F12
  • 40. 开发第一个Android应用在项目上右键点击run as Android application,如下图:
  • 41. Android模拟器无法启动原因: Android_SDK_HOME环境变量没有设置。 每当有一个新的AVD被创建,Android SDK and AVD Manager都会在“我的文档”路径下的.android/avd文件夹创建对应的avd文件夹(my_avd.avd)及配置文件 (my_avd.ini);同时,当你启动一个AVD时,Eclipse或者Android SDK and AVD Manager就会去ANDROID_SDK_HOME对应的路径下去查找AVD文件,并启动AVD。 当Android_SDK_HOME 环境变量没有设置时,Android SDK and AVD Manager会在当前用户的“我的文档”下创建.android/avd文件夹,并创建对应的avd文件夹(my_avd.avd)及配置文件 (my_avd.ini)。而当你通过Eclipse或Android SDK and AVD Manager启动AVD时,Android SDK and AVD Manager却会去Windows默认的“我的文档”路径(C:\Documents and Settings\user\My Documents)去查找AVD文件 因此,没有设置Android_SDK_HOME变量的情况下,如果你没有修改过Windows的“我的文档”路径,启动AVD是没有问题的。但是如果修改过,就会出现下面的错误 emulator: ERROR: unknown virtual device name: 'my_avd‘ emulator: could not find virtual device named 'my_avd‘ 解决方法: 添加Android_SDK_HOME环境变量,从变量名上看最好把ANDROID_SDK_HOME指向Android SDK目录,然后启动AVD
  • 42. Android应用程序架构<一>src/ java原代码存放目录 gen/ 自动生成目录 gen 目录中存放所有由Android开发工具自动生成的文件。目录中最重要的就是R.java文件。 这个文件由Android开发工具自动产生的。Android开发工具会自动根据你放入res目录的资源,同步更新修改R.java文件。正因为R.java文件是由开发工具自动生成的,所以我们应避免手工修改R.java。R.java在应用中起到了字典的作用,它包含了各种资源的id,通过R.java,应用可以很方便地找到对应资源。 res/ 资源(Resource)目录 在这个目录中我们可以存放应用使用到的各种资源,如xml界面文件,图片或数据。具体请看ppt下方备注栏。
  • 43. Android应用程序架构<二>assets资源目录 Android除了提供/res目录存放资源文件外,在/assets目录也可以存放资源文件,而且/assets目录下的资源文件不会在R.java自动生成ID,所以读取/assets目录下的文件必须指定文件的路径,如:file:///android_asset/xxx.3gp AndroidManifest.xml 项目清单文件 这个文件列出了应用程序所提供的功能,以后你开发好的各种组件(Activity、ContentProvider、BroadcastReceiver、Service)需要在该文件中进行配置,如果应用使用到了系统内置的应用(如电话服务、互联网服务、短信服务、GPS服务等等),你还需在该文件中声明使用权限。 default.properties 项目环境信息,一般是不需要修改此文件
  • 44. Android应用程序架构<三>
  • 45. Android应用程序架构<四>res/drawable 专门存放png、jpg等图像资源。在代码中使用getResources().getDrawable(resourceId)获取该目录下的资源。 drawable- hdpi、drawable- mdpi、drawable-ldpi的区别:   (1)drawable-hdpi里面存放高分辨率的图片,如WVGA (480x800),FWVGA (480x854)   (2)drawable-mdpi里面存放中等分辨率的图片,如HVGA (320x480)   (3)drawable-ldpi里面存放低分辨率的图片,如QVGA (240x320)   系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片。   在开发程序时为了兼容不同平台不同屏幕,建议各自文件夹根据需求均存放不同版本图片。 res/layout 专门存放xml界面文件,xml界面文件和HTML文件一样,主要用于显示用户操作界面。
  • 46. Android应用程序架构<五>res/values 专门存放应用使用到的各种类型数据。不同类型的数据存放在不同的文件中,如下: · strings.xml 定义字符串和数值,在Activity中使用getResources().getString(resourceId) 或getResources().getText(resourceId)取得资源。它的作用和struts中的国际化资源文件一样。 王家林 · arrays.xml 定义数组。 red yellow green blue
  • 47. Android应用程序架构<六>· colors.xml 定义颜色和颜色字串数值,你可以在Activity中使用getResources().getDrawable(resourceId) 以及getResources().getColor(resourceId)取得这些资源。例子如下: #ff0000 · dimens.xml 定义尺寸数据,在Activity中使用getResources().getDimension(resourceId) 取得这些资源 50dip
  • 48. Android应用程序架构<七>· styles.xml 定义样式。 res/anim/ 存放定义动画的XML文件。 res/xml/ 在Activity中使用getResources().getXML()读取该目录下的XML资源文件。 res/raw/ 该目录用于存放应用使用到的原始文件,如音效文件等。编译软件时,这些数据不会被编译,它们被直接加入到程序安装包里。 为了在程序中使用这些资源,你可以调用getResources().openRawResource(ID) , 参数ID形式:R.raw.somefilename。
  • 49. 通过命令操作1 android 显示SDK and AVD manager 2 android list avds 列出我们创建的模拟器
  • 50. 通过命令操作3 android list targets 列出我们可以使用 的 sdk的版本 4 adb devices 列出所有的设备
  • 51. 通过命令操作5 adb shell 挂载到linux的空间 6 adb install xxx.apk 如果有多个设备,我们可以指定设备 adb install –s emulator-5554 D:/xxx.apk 7 emulator –avd advname 启动模拟器 例如:emulator –avd 2.2 (2.2 为我们创建设备的名称) 8 mkmdcard 20m d:/sdcard.img 创建sdcard 9 adb pull 10 adb push 11 android create avd –name android2.2 –target 8 创建模拟器 12 ctrl + F11 横竖屏的切换
  • 52. android工程的打包过程 通过解压工具解压APK:
  • 53. 电话拔号器效果图:
  • 54. 电话拔号器因为应用要使用手机的电话服务,所以要在清单文件AndroidManifest.xml中添加电话服务权限: 略....
  • 55. 电话拔号器界面布局:
  • 56. Android中的显示单位 px (pixels)像素 一般HVGA代表320x480像素,这个用的比较多。 dip或dp (device independent pixels)设备独立像素 这个和设备硬件有关,一般为了支持WVGA、HVGA和QVGA 推荐使用这个,不依赖像素。 sp (scaled pixels — best for text size)比例像素 主要处理字体的大小,可以根据系统的字体自适应。 除了上面三个显示单位,下面还有几个不太常用: in (inches)英寸 mm (millimeters)毫米 pt (points)点,1/72英寸 为了适应不同分辨率,不同的像素密度,推荐使用dip ,文字使用sp。
  • 57. 电话拔号器Activity: public class DialerAction extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button = (Button)findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { EditText editText = (EditText)findViewById(R.id.mobile); Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+ editText.getText())); DialerAction.this.startActivity(intent); } }); } }
  • 58. 电话拔号器测试步骤: 1>在Eclipse中运行此应用 2>开启一个Android模拟器: 3>在电话播号器中输入上图现显的电话号码
  • 59. “尚未注册网络”错误信息的解决办法<一> 打开Android模拟器时,出现无信号,拔打电话或发短信时,提示“尚未注册网络”错误信息的解决方案如下。 场景一:你的电脑没有连接上互联网,同时也没有在局域网。 解决办法:右键点击网上邻居,选择"属性",在网络连接窗口中右键点击"本地连接",选择"属性",设置TCP/IP属性如下: IP地址:192.168.1.100 子网掩码:255.255.255.0 默认网关:192.168.1.100 首选DNS服务器:192.168.1.100
  • 60. “尚未注册网络”错误信息的解决办法<二> 场景二:你的电脑没有连接上互联网,但在局域网。 解决办法:右键点击网上邻居,选择"属性",在网络连接窗口中右键点击"本地连接",选择"属性",设置TCP/IP属性如下: IP地址:设置成你所在局域网的IP,如:192.168.1.100 子网掩码:设置成你所在局域网的掩码,如:255.255.255.0 默认网关:设置成你所在局域网的网关,一般网关的IP格式为:*.*.*.1,如:192.168.1.1 首选DNS服务器:设置成你所在局域网的路由器IP,一般路由器的IP格式为:*.*.*.1,如:192.168.1.1 最后一种解决方案是:让你的电脑连接上互联网。
  • 61. 短信发送器效果图:
  • 62. 短信发送器因为应用要使用手机的短信服务,所以要在清单文件AndroidManifest.xml中添加短信服务权限: 略....
  • 63. 短信发送器界面布局:
  • 64. 短信发送器Activity主要代码: String mobile = mobileView.getText().toString(); String content = contentView.getText().toString(); SmsManager smsManager = SmsManager.getDefault(); PendingIntent sentIntent = PendingIntent.getBroadcast(SMSSender.this, 0, new Intent(), 0); //如果字数超过70,需拆分成多条短信发送 List msgs = smsManager.divideMessage(content); for(String msg : msgs){ smsManager.sendTextMessage(mobile, null, msg, sentIntent, null); //第四个参数用于告之短信发送状态,最后一个参数用于告之短信接收状态 } Toast.makeText(SMSSender.this, "短信发送完成", Toast.LENGTH_LONG).show();
  • 65. 发送彩信可以通过调用系统自带的短信程序发送彩信: Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///sdcard/cong.png")); intent.putExtra("address", “13677789999”); intent.putExtra("exit_on_sent", true); intent.putExtra("subject", "it's subject"); intent.putExtra("sms_body", "it's content"); intent.setType(“image/jpeg”); // 视频:video/mpeg* ,文本:text/plain startActivity(intent);
  • 66. 对应用进行单元测试<一>在实际开发中,开发android软件的过程需要不断地进行测试。使用Junit测试框架,是正规Android开发的必用技术,在Junit中可以得到组件,可以模拟发送事件和检测程序处理的正确性。 第一步:首先在AndroidManifest.xml中加入下面红色代码: .... 上面targetPackage指定的包要和应用的package相同。
  • 67. 对应用进行单元测试<二>第二步:编写单元测试代码(选择要测试的方法,右键点击“Run As”--“Android Junit Test” ): import android.test.AndroidTestCase; import android.util.Log; public class XMLTest extends AndroidTestCase { public void testSomething() throws Throwable { Assert.assertTrue(1 + 1 == 3); } }
  • 68. 数据存储与访问很多时候我们开发的软件需要对处理后的数据进行存储,以供再次访问。Android为数据存储提供了如下几种方式: 文件 SharedPreferences(偏好参数保存) SQLite数据库 内容提供者(Content provider) 网络
  • 69. 使用文件进行数据存储<一>首先给大家介绍使用文件如何对数据进行存储,Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的实现过程与在J2SE环境中保存数据到文件中是一样的。 public class FileActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { ... FileOutputStream outStream = this.openFileOutput(“wangjialin.txt", Context.MODE_PRIVATE); outStream.write(“王家林".getBytes()); outStream.close(); } } openFileOutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它。
  • 70. 使用文件进行数据存储<二>创建的文件保存在/data/data//files目录,如: /data/data/com.wangjialin.action/files/wangjialin.txt ,通过点击Eclipse菜单“Window”-“Show View”-“Other”,在对话窗口中展开android文件夹,选择下面的File Explorer视图,然后在File Explorer视图中展开/data/data//files目录就可以看到该文件。 openFileOutput()方法的第二参数用于指定操作模式,有四种模式,分别为: Context.MODE_PRIVATE = 0 Context.MODE_APPEND = 32768 Context.MODE_WORLD_READABLE = 1 Context.MODE_WORLD_WRITEABLE = 2
  • 71. 读取文件内容<一>如果要打开存放在/data/data//files目录应用私有的文件,可以使用Activity提供openFileInput()方法。 FileInputStream inStream = this.getContext().openFileInput(“wangjialin.txt"); Log.i("FileTest", readInStream(inStream)); readInStream()的方法请看本页下面备注。 或者直接使用文件的绝对路径:File file = new File("/data/data/com.wangjialin.action/files/wangjialin.txt"); FileInputStream inStream = new FileInputStream(file); Log.i("FileTest", readInStream(inStream)); 注意:上面文件路径中的“com.wangjialin.action”为应用所在包,当你在编写代码时应替换为你自己应用使用的包。
  • 72. 读取文件内容<二>对于私有文件只能被创建该文件的应用访问,如果希望文件能被其他应用读和写,可以在创建文件时,指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限。 Activity还提供了getCacheDir()和getFilesDir()方法: getCacheDir()方法用于获取/data/data//cache目录 getFilesDir()方法用于获取/data/data//files目录
  • 73. 读取文件内容<三>public static String readInStream(FileInputStream inStream){ try { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length = -1; while((length = inStream.read(buffer)) != -1 ){ outStream.write(buffer, 0, length); } outStream.close(); inStream.close(); return outStream.toString(); } catch (IOException e) { Log.i("FileTest", e.getMessage()); } return null; }
  • 74. 把文件存放在SDCard<一>使用Activity的openFileOutput()方法保存文件,文件是存放在手机空间上,一般手机的存储空间不是很大,存放些小文件还行,如果要存放像视频这样的大文件,是不可行的。对于像视频这样的大文件,我们可以把它存放在SDCard。 SDCard是干什么的?你可以 把它看作是移动硬盘或U盘。 在模拟器中使用SDCard,你需要先创建一张SDCard卡(当然不是真的SDCard,只是镜像文件)。创建SDCard可以在Eclipse创建模拟器时随同创建,也可以使用DOS命令进行创建,如下: 在Dos窗口中进入android SDK安装路径的tools目录,输入以下命令创建一张容量为2G的SDCard,文件后缀可以随便取,建议使用.img: mksdcard 2048M F:\Android\sdcard.img
  • 75. 把文件存放在SDCard<二>在程序中访问SDCard,你需要申请访问SDCard的权限。 在AndroidManifest.xml中加入访问SDCard的权限如下:
  • 76. 把文件存放在SDCard<一>要往SDCard存放文件,程序必须先判断手机是否装有SDCard,并且可以进行读写。 注意:访问SDCard必须在AndroidManifest.xml中加入访问SDCard的权限 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File sdCardDir = Environment.getExternalStorageDirectory();//获取SDCard目录 File saveFile = new File(sdCardDir, “wangjialin.txt”); FileOutputStream outStream = new FileOutputStream(saveFile); outStream.write(“王家林".getBytes()); outStream.close(); }
  • 77. 把文件存放在SDCard<二>Environment.getExternalStorageState()方法用于获取SDCard的状态,如果手机装有SDCard,并且可以进行读写,那么方法返回的状态等于Environment.MEDIA_MOUNTED。 Environment.getExternalStorageDirectory()方法用于获取SDCard的目录,当然要获取SDCard的目录,你也可以这样写: File sdCardDir = new File("/mnt/sdcard"); //获取SDCard目录 File saveFile = new File(sdCardDir, “wangjialin.txt"); //上面两句代码可以合成一句: File saveFile = new File("/mnt/sdcard/wangjialin.txt"); FileOutputStream outStream = new FileOutputStream(saveFile); outStream.write(“Hey,WangJialin".getBytes()); outStream.close();
  • 78. 使用SAX或者DOM或者pull解析XML文件在Android平台上可以使用Simple API for XML(SAX) 、 Document Object Model(DOM)和Android附带的pull解析器解析XML文件。 下面是本例子要解析的XML文件: 文件名称:wangjialin.xml wangjialin 58 Android 4
  • 79. XML文件的javabean 例子定义了一个javabean用于存放上面解析出来的xml内容, 这个javabean为Person, public class Person { private Integer id; private String name; private Short age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Short getAge() { return age; } public void setAge(Short age) { this.age = age; } }
  • 80. 使用SAX读取XML文件<一>SAX是一个解析速度快并且占用内存少的xml解析器,非常适合用于Android等移动设备。 SAX解析XML文件采用的是事件驱动,也就是说,它并不需要解析完整个文档,在按内容顺序解析文档的过程中,SAX会判断当前读到的字符是否合法XML语法中的某部分,如果符合就会触发事件。所谓事件,其实就是一些回调(callback)方法,这些方法(事件)定义在ContentHandler接口。下面是一些ContentHandler接口常用的方法: startDocument() 当遇到文档的开头的时候,调用这个方法,可以在其中做一些预处理的工作。 endDocument() 和上面的方法相对应,当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。
  • 81. 使用SAX读取XML文件<二>startElement(String namespaceURI, String localName, String qName, Attributes atts) 当读到一个开始标签的时候,会触发这个方法。namespaceURI就是命名空间,localName是不带命名空间前缀的标签名,qName是带命名空间前缀的标签名。通过atts可以得到所有的属性名和相应的值。要注意的是SAX中一个重要的特点就是它的流式处理,当遇到一个标签的时候,它并不会纪录下以前所碰到的标签,也就是说,在startElement()方法中,所有你所知道的信息,就是标签的名字和属性,至于标签的嵌套结构,上层标签的名字,是否有子元属等等其它与结构相关的信息,都是不得而知的,都需要你的程序来完成。这使得SAX在编程处理上没有DOM来得那么方便。 endElement(String uri, String localName, String name) 这个方法和上面的方法相对应,在遇到结束标签的时候,调用这个方法。 characters(char[] ch, int start, int length) 这个方法用来处理在XML文件中读到的内容,第一个参数为文件的字符串内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度,使用new String(ch,start,length)就可以获取内容。
  • 82. 使用SAX读取XML文件<三> 王家林 58 Android 4 解析wangjialin.xml触发的事件为: 读到的标签及内容 触发事件 {文档开始} startDocument() startElement(, "persons", null, "{Attributes}") "\n\t" characters("...", "12", "2") startElement(, "person", null, "{Attributes}") "\n\t\t" characters("...", "31", "3") startElement(, "name", null, "{Attributes}") “王家林" characters("...", "40", "2") endElement("", "name", null)
  • 83. 使用SAX读取XML文件<四>"\n\t\t" characters("...", "50", "3") startElement(, "age", null, "{Attributes}") "30" characters("...", "58", "2") endElement("", "age", null) "\n\t" characters("...", "67", "2") endElement("", "person", null) "\n\t" characters("...", "79", "2") startElement(, "person", null, "{Attributes}") "\n\t\t" characters("...", "98", "3") startElement(, "name", null, "{Attributes}") “Android" characters("...", "107", "3") endElement("", "name", null) "\n\t\t" characters("...", "118", "3") startElement(, "age", null, "{Attributes}") "25" characters("...", "126", "2") endElement("", "age", null) "\n\t" characters("...", "135", "2") endElement("", "person", null) "\n" characters("...", "147", "1") endElement("", "persons", null) {文档结束} endDocument()
  • 84. 使用SAX读取XML文件<五>只要为SAX提供实现ContentHandler接口的类,那么该类就可以得到通知事件(实际上就是SAX调用了该类中的回调方法)。因为ContentHandler是一个接口,在使用的时候可能会有些不方便,因此,SAX还为其制定了一个Helper类:DefaultHandler,它实现了ContentHandler接口,但是其所有的方法体都为空,在实现的时候,你只需要继承这个类,然后重写相应的方法即可。使用SAX解析iwangjialinxml的代码如下: public static List readXML(InputStream inStream) { try { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser saxParser = spf.newSAXParser(); //创建解析器 //设置解析器的相关特性,http://xml.org/sax/features/namespaces = true 表示开启命名空间特性 //saxParser.setProperty("http://xml.org/sax/features/namespaces",true); XMLContentHandler handler = new XMLContentHandler(); saxParser.parse(inStream, handler); inStream.close(); return handler.getPersons(); } catch (Exception e) { e.printStackTrace(); } return null; } SAX 支持已内置到JDK1.5中,你无需添加任何的jar文件。
  • 85. 使用DOM读取XML文件除了使用 SAX可以解析XML文件,大家也可以使用熟悉的DOM来解析XML文件。 DOM解析XML文件时,会将XML文件的所有内容以文档树方式存放在内存中,然后允许您使用DOM API遍历XML树、检索所需的数据。使用DOM操作XML的代码看起来是比较直观的,并且在编码方面比基于SAX的实现更加简单。但是,因为DOM需要将XML文件的所有内容以文档树方式存放在内存中,所以内存的消耗比较大,特别对于运行Android的移动设备来说,因为设备的资源比较宝贵,所以建议还是采用SAX来解析XML文件,当然,如果XML文件的内容比较小采用DOM也是可行的。 代码请看本页下方备注
  • 86. 使用Pull解析器读取XML文件除了可以使用 SAX或DOM解析XML文件之外,大家也可以使用Android内置的Pull解析器解析XML文件。 Pull解析器是一个开源的java项目,既可以用于android,也可以用于JavaEE。如果用在javaEE需要把其jar文件放入类路径中,因为Android已经集成进了Pull解析器,所以无需添加任何jar文件。android系统本身使用到的各种xml文件,其内部也是采用Pull解析器进行解析的。 Pull解析器的运行方式与 SAX 解析器相似。它提供了类似的事件,如:开始元素和结束元素事件,使用parser.next()可以进入下一个元素并触发相应事件。跟SAX不同的是, Pull解析器产生的事件是一个数字,而非方法,因此可以使用一个switch对感兴趣的事件进行处理。当元素开始解析时,调用parser.nextText()方法可以获取下一个Text类型节点的值。 使用Pull解析器读取wangjialin.xml的代码在本页下方备注 Pull解析器的源码及文档下载网址:http://xmlpull.org/xmlpull-website/impls.shtml
  • 87. 使用Pull解析器生成XML文件有些时候,我们需要生成一个XML文件,生成XML文件的方法有很多,如:可以只使用一个StringBuilder组拼XML内容,然后把内容写入到文件中;或者使用DOM API生成XML文件,或者也可以使用pull解析器生成XML文件,这里推荐大家使用Pull解析器。 使用Pull解析器生成一个与wangjialin.xml文件内容相同的heywangjialin.xml文件,代码在本页下方备注 使用代码如下(生成XML文件): File xmlFile = new File(" heywangjialin.xml "); FileOutputStream outStream = new FileOutputStream(xmlFile); OutputStreamWriter outStreamWriter = new OutputStreamWriter(outStream, "UTF-8"); BufferedWriter writer = new BufferedWriter(outStreamWriter); writeXML(persons, writer); writer.flush(); writer.close(); 如果只想得到生成的xml字符串内容,可以使用StringWriter: StringWriter writer = new StringWriter(); writeXML(persons, writer); String content = writer.toString();
  • 88. JSON介绍1,JSON:JavaScript Object Notation; 2,JSON数据时一系列键值对的集合; 3,JSON尤其适合于移动网络编程; 4,官方网站:http://www.json.org
  • 89. JSON和XML比较1,JSON和XML的数据可读性基本相同; 2,JSON和XML同样拥有丰富的解析手段; 3,JSON相对于XML来讲,数据体积小; 4,JSON与JavaScript的交互更加方便; 5,JSON对数据的描述相对较差;
  • 90. JSON示例{ “name”:”WangJialin” “address”: { “city”:”Beijing”, “street”:”ZhongGuancun”, “postcode”:”100080” } }
  • 91. JSON的编程步骤
  • 92. JSON编程的核心代码把网络中的JSON数据流转换成字符串后: JSONArray array = new JSONArray (json); for (int i=0; I < array.length(); i++) { JSONObject item = array.getJSONObject(i); int id = item.getInt(“id”); String title = item.getString(“title”); }
  • 93. 使用SharedPreferences进行数据存储<一>很多时候我们开发的软件需要向用户提供软件参数设置功能,例如我们常用的QQ,用户可以设置是否允许陌生人添加自己为好友。对于软件配置参数的保存,如果是window软件通常我们会采用ini文件进行保存,如果是j2se应用,我们会采用properties属性文件或者xml进行保存。如果是Android应用,我们最适合采用什么方式保存软件配置参数呢?Android平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data//shared_prefs目录下: SharedPreferences sharedPreferences = getSharedPreferences(“wangjialin", Context.MODE_PRIVATE); Editor editor = sharedPreferences.edit();//获取编辑器 editor.putString(“name”, “王家林"); editor.putInt("age", 100); editor.commit();//提交修改
  • 94. 使用SharedPreferences进行数据存储<二>生成的wangjialin.xml文件内容如下: 王家林 因为SharedPreferences背后是使用xml文件保存数据,getSharedPreferences(name,mode)方法的第一个参数用于指定该文件的名称,名称不用带后缀,后缀会由Android自动加上。方法的第二个参数指定文件的操作模式,共有四种操作模式,这四种模式前面介绍使用文件方式保存数据时已经讲解过。如果希望SharedPreferences背后使用的xml文件能被其他应用读和写,可以指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限。 另外Activity还提供了另一个getPreferences(mode)方法操作SharedPreferences,这个方法默认使用当前类不带包名的类名作为文件的名称。
  • 95. 访问SharedPreferences中的数据<一>访问SharedPreferences中的数据代码如下: SharedPreferences sharedPreferences = getSharedPreferences(“wangjialin", Context.MODE_PRIVATE); //getString()第二个参数为缺省值,如果preference中不存在该key,将返回缺省值 String name = sharedPreferences.getString("name", ""); int age = sharedPreferences.getInt("age", 1); 如果访问其他应用中的Preference,前提条件是:该preference创建时指定了Context.MODE_WORLD_READABLE或者Context.MODE_WORLD_WRITEABLE权限。如:有个为com.wangjialin.action的应用使用下面语句创建了preference。 getSharedPreferences(“wangjialin", Context.MODE_WORLD_READABLE);
  • 96. 访问SharedPreferences中的数据<二>其他应用要访问上面应用的preference,首先需要创建上面应用的Context,然后通过Context 访问preference ,访问preference时会在应用所在包下的shared_prefs目录找到preference : Context otherAppsContext = createPackageContext("com.wangjialin.action", Context.CONTEXT_IGNORE_SECURITY); SharedPreferences sharedPreferences = otherAppsContext.getSharedPreferences(“wangjialin", Context.MODE_WORLD_READABLE); String name = sharedPreferences.getString("name", ""); int age = sharedPreferences.getInt("age", 0); 如果不通过创建Context访问其他应用的preference,也可以以读取xml文件方式直接访问其他应用preference对应的xml文件,如: File xmlFile = new File(“/data/data//shared_prefs/wangjialin.xml”);//应替换成应用的包名
  • 97. 使用嵌入式关系型SQLite数据库存储数据<一>在Android平台上,集成了一个嵌入式关系型数据库—SQLite,SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型,虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等数据类型,只不过在运算或保存时会转成对应的五种数据类型。 SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用关心字段声明的数据类型是什么。例如:可以在Integer类型的字段中存放字符串,或者在布尔型字段中存放浮点数,或者在字符型字段中存放日期型值。 但有一种情况例外:定义为INTEGER PRIMARY KEY的字段只能存储64位整数, 当向这种字段保存除整数以外的数据时,将会产生错误。 另外,在编写CREATE TABLE 语句时,你可以省略跟在字段名称后面的数据类型信息,如下面语句你可以省略 name字段的类型信息: CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))
  • 98. 使用嵌入式关系型SQLite数据库存储数据<二>SQLite可以解析大部分标准SQL语句,如: 查询语句:select * from 表名 where 条件子句 group by 分组字句 having ... order by 排序子句 如:select * from person select * from person order by id desc select name from person group by name having count(*)>1 分页SQL与mysql类似,下面SQL语句获取5条记录,跳过前面3条记录 select * from Account limit 5 offset 3 或者 select * from Account limit 3,5 插入语句:insert into 表名(字段列表) values(值列表)。如: insert into person(name, age) values(‘家林’,100) 更新语句:update 表名 set 字段名=值 where 条件子句。如:update person set name=‘家林‘ where id=10 删除语句:delete from 表名 where 条件子句。如:delete from person where id=10 获取添加记录后自增长的ID值:SELECT last_insert_rowid()
  • 99. 使用SQLiteOpenHelper对数据库进行版本管理<一> 我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用软件时创建出应用使用到的数据库表结构及添加一些初始化记录,另外在软件升级的时候,也需要对数据表结构进行更新。那么,我们如何才能实现在用户初次使用或升级软件时自动在用户的手机上创建出应用需要的数据库表呢?总不能让我们在每个需要安装此软件的手机上通过手工方式创建数据库表吧?因为这种需求是每个数据库应用都要面临的,所以在Android系统,为我们提供了一个名为SQLiteOpenHelper的抽象类,必须继承它才能使用,它是通过对数据库版本进行管理来实现前面提出的需求。
  • 100. 使用SQLiteOpenHelper对数据库进行版本管理<二>为了实现对数据库版本进行管理,SQLiteOpenHelper类提供了两个重要的方法,分别是onCreate(SQLiteDatabase db)和onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion),前者用于初次使用软件时生成数据库表,后者用于升级软件时更新数据库表结构。当调用SQLiteOpenHelper的getWritableDatabase()或者getReadableDatabase()方法获取用于操作数据库的SQLiteDatabase实例的时候,如果数据库不存在,Android系统会自动生成一个数据库,接着调用onCreate()方法,onCreate()方法在初次生成数据库时才会被调用,在onCreate()方法里可以生成数据库表结构及添加一些应用使用到的初始化数据。onUpgrade()方法在数据库的版本发生变化时会被调用,一般在软件升级时才需改变版本号,而数据库的版本是由程序员控制的,假设数据库现在的版本是1,由于业务的变更,修改了数据库表结构,这时候就需要升级软件,升级软件时希望更新用户手机里的数据库表结构,为了实现这一目的,可以把原来的数据库版本设置为2(有同学问设置为3行不行?当然可以,如果你愿意,设置为100也行),并且在onUpgrade()方法里面实现表结构的更新。当软件的版本升级次数比较多,这时在onUpgrade()方法里面可以根据原版号和目标版本号进行判断,然后作出相应的表结构及数据更新。 getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。但getWritableDatabase() 方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,倘若使用getWritableDatabase()打开数据库就会出错。getReadableDatabase()方法先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
  • 101. 使用SQLiteOpenHelper对数据库进行版本管理public class DatabaseHelper extends SQLiteOpenHelper { //类没有实例化,是不能用作父类构造器的参数,必须声明为静态 private static final String name = “wangjialin"; //数据库名称 private static final int version = 1; //数据库版本 public DatabaseHelper(Context context) { //第三个参数CursorFactory指定在执行查询时获得一个游标实例的工厂类,设置为null,代表使用系统默认的工厂类 super(context, name, null, version); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS person (personid integer primary key autoincrement, name varchar(20), age INTEGER)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(" ALTER TABLE person ADD phone VARCHAR(12) NULL "); //往表中增加一列 // DROP TABLE IF EXISTS person 删除表 } } 在实际项目开发中,当数据库表结构发生更新时,应该避免用户存放于数据库中的数据丢失。
  • 102. 使用SQLiteDatabase操作SQLite数据库<一>Android提供了一个名为SQLiteDatabase的类,该类封装了一些操作数据库的API,使用该类可以完成对数据进行添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)操作(这些操作简称为CRUD)。对SQLiteDatabase的学习,我们应该重点掌握execSQL()和rawQuery()方法。 execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句; rawQuery()方法用于执行select语句。 execSQL()方法的使用例子: SQLiteDatabase db = ....; db.execSQL(“insert into person(name, age) values(‘王家林', 100)"); db.close();
  • 103. 使用SQLiteDatabase操作SQLite数据库<二>执行上面SQL语句会往person表中添加进一条记录,在实际应用中, 语句中的“王家林”这些参数值会由用户输入界面提供,如果把用户输入的内容原样组拼到上面的insert语句, 当用户输入的内容含有单引号时,组拼出来的SQL语句就会存在语法错误。要解决这个问题需要对单引号进行转义,也就是把单引号转换成两个单引号。有些时候用户往往还会输入像“ & ”这些特殊SQL符号,为保证组拼好的SQL语句语法正确,必须对SQL语句中的这些特殊SQL符号都进行转义,显然,对每条SQL语句都做这样的处理工作是比较烦琐的。 SQLiteDatabase类提供了一个重载后的execSQL(String sql, Object[] bindArgs)方法,使用这个方法可以解决前面提到的问题,因为这个方法支持使用占位符参数(?)。使用例子如下: SQLiteDatabase db = ....; db.execSQL(“insert into person(name, age) values(?,?)”, new Object[]{“王家林", 100}); db.close(); execSQL(String sql, Object[] bindArgs)方法的第一个参数为SQL语句,第二个参数为SQL语句中占位符参数的值,参数值在数组中的顺序要和占位符的位置对应。
  • 104. 使用SQLiteDatabase操作SQLite数据库<三>SQLiteDatabase的rawQuery() 用于执行select语句,使用例子如下: SQLiteDatabase db = ....; Cursor cursor = db.rawQuery(“select * from person”, null); while (cursor.moveToNext()) { int personid = cursor.getInt(0); //获取第一列的值,第一列的索引从0开始 String name = cursor.getString(1);//获取第二列的值 int age = cursor.getInt(2);//获取第三列的值 } cursor.close(); db.close(); rawQuery()方法的第一个参数为select语句;第二个参数为select语句中占位符参数的值,如果select语句没有使用占位符,该参数可以设置为null。带占位符参数的select语句使用例子如下: Cursor cursor = db.rawQuery("select * from person where name like ? and age=?", new String[]{"%jialin%", “100"});
  • 105. 使用SQLiteDatabase操作SQLite数据库<四> Cursor是结果集游标,用于对结果集进行随机访问,如果大家熟悉jdbc, 其实Cursor与JDBC中的ResultSet作用很相似。使用moveToNext()方法可以将游标从当前行移动到下一行,如果已经移过了结果集的最后一行,返回结果为false,否则为true。另外Cursor 还有常用的moveToPrevious()方法(用于将游标从当前行移动到上一行,如果已经移过了结果集的第一行,返回值为false,否则为true )、moveToFirst()方法(用于将游标移动到结果集的第一行,如果结果集为空,返回值为false,否则为true )和moveToLast()方法(用于将游标移动到结果集的最后一行,如果结果集为空,返回值为false,否则为true ) 。
  • 106. 使用SQLiteDatabase操作SQLite数据库<五>除了前面给大家介绍的execSQL()和rawQuery()方法, SQLiteDatabase还专门提供了对应于添加、删除、更新、查询的操作方法: insert()、delete()、update()和query() 。这些方法实际上是给那些不太了解SQL语法的菜鸟使用的,对于熟悉SQL语法的程序员而言,直接使用execSQL()和rawQuery()方法执行SQL语句就能完成数据的添加、删除、更新、查询操作。 Insert()方法用于添加数据,各个字段的数据使用ContentValues进行存放。 ContentValues类似于MAP,相对于MAP,它提供了存取数据对应的put(String key, Xxx value)和getAsXxx(String key)方法, key为字段名称,value为字段值,Xxx指的是各种常用的数据类型,如:String、Integer等。 SQLiteDatabase db = databaseHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(“name”, “王家林"); values.put("age", 100); long rowid = db.insert(“person”, null, values);//返回新添记录的行号,与主键id无关
  • 107. 使用SQLiteDatabase操作SQLite数据库<六> 不管第三个参数是否包含数据,执行Insert()方法必然会添加一条记录,如果第三个参数为空,会添加一条除主键之外其他字段值为Null的记录。Insert()方法内部实际上通过构造insert SQL语句完成数据的添加,Insert()方法的第二个参数用于指定空值字段的名称,相信大家对该参数会感到疑惑,该参数的作用是什么?是这样的:如果第三个参数values 为Null或者元素个数为0, 由于Insert()方法要求必须添加一条除了主键之外其它字段为Null值的记录,为了满足SQL语法的需要, insert语句必须给定一个字段名,如:insert into person(name) values(NULL),倘若不给定字段名 , insert语句就成了这样: insert into person() values(),显然这不满足标准SQL的语法。对于字段名,建议使用主键之外的字段,如果使用了INTEGER类型的主键字段,执行类似insert into person(personid) values(NULL)的insert语句后,该主键字段值也不会为NULL。如果第三个参数values 不为Null并且元素的个数大于0 ,可以把第二个参数设置为null。
  • 108. 使用SQLiteDatabase操作SQLite数据库<七>delete()方法的使用: SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.delete("person", "personid
  • 109. 使用SQLiteDatabase操作SQLite数据库<八>query()方法实际上是把select语句拆分成了若干个组成部分,然后作为方法的输入参数: SQLiteDatabase db = databaseHelper.getWritableDatabase(); Cursor cursor = db.query("person", new String[]{"personid,name,age"}, "name like ?", new String[]{"%jialin%"}, null, null, "personid desc", "1,2"); while (cursor.moveToNext()) { int personid = cursor.getInt(0); //获取第一列的值,第一列的索引从0开始 String name = cursor.getString(1);//获取第二列的值 int age = cursor.getInt(2);//获取第三列的值 } cursor.close(); db.close(); 上面代码用于从person表中查找name字段含有“jialin”的记录,匹配的记录按personid降序排序,对排序后的结果略过第一条记录,只获取2条记录。
  • 110. 使用SQLiteDatabase操作SQLite数据库<九>query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit)方法各参数的含义: table:表名。相当于select语句from关键字后面的部分。如果是多表联合查询,可以用逗号将两个表名分开。 columns:要查询出来的列名。相当于select语句select关键字后面的部分。 selection:查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符“?” selectionArgs:对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常。 groupBy:相当于select语句group by关键字后面的部分 having:相当于select语句having关键字后面的部分 orderBy:相当于select语句order by关键字后面的部分,如:personid desc, age asc; limit:指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分。
  • 111. 使用SQLiteOpenHelper获取用于操作数据库的SQLiteDatabase实例<一>public class DatabaseHelper extends SQLiteOpenHelper { private static final String name = “wangjialin"; //数据库名称 private static final int version = 1; //数据库版本 ......略 } public class HelloActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { ...... Button button =(Button) this.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { DatabaseHelper databaseHelper = new DatabaseHelper(HelloActivity.this); SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.execSQL(“insert into person(name, age) values(?,?)”, new Object[]{“王家林", 100}); db.close(); }}); } }
  • 112. 使用SQLiteOpenHelper获取用于操作数据库的SQLiteDatabase实例<二> 第一次调用getWritableDatabase()或getReadableDatabase()方法后,SQLiteOpenHelper会缓存当前的SQLiteDatabase实例,SQLiteDatabase实例正常情况下会维持数据库的打开状态,所以在你不再需要SQLiteDatabase实例时,请及时调用close()方法释放资源。一旦SQLiteDatabase实例被缓存,多次调用getWritableDatabase()或getReadableDatabase()方法得到的都是同一实例。
  • 113. 使用事务操作SQLite数据库使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功,如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 方法设置事务的标志为成功则提交事务,如果没有调用setTransactionSuccessful() 方法则回滚事务。使用例子如下: SQLiteDatabase db = ....; db.beginTransaction();//开始事务 try { db.execSQL(“insert into person(name, age) values(?,?)”, new Object[]{“王家林", 4}); db.execSQL(“update person set name=? where personid=?”, new Object[]{“家林哥哥", 1}); db.setTransactionSuccessful();//调用此方法会在执行到endTransaction() 时提交当前事务,如果不调用此方法会回滚事务 } finally { db.endTransaction();//由事务的标志决定是提交事务,还是回滚事务 } db.close(); 上面两条SQL语句在同一个事务中执行。
  • 114. Android sqlite3工具的使用1 cmd  adb shell 首先挂载到linux 2 cd data/data/com.android.contacts.provider 3 cd database 4 sqlite3 contacts 打开数据库 eg: sqlite3 contacts.db 5 .tables 查看所有的表 eg: .table 6 .schema 查看所有的创建表、视图的语句 eg: .schema 7 .help 查看帮助 eg: .help 8 .header(s) NO |OFF是否显示列头信息 eg: headers ON 9 .mode MODE ?table? 指定数据显示风格 eg: .mode column 10 .nullValue NULL空值数据显示问题 eg: .nullValue NULL
  • 115. 使用ContentProvider(内容提供者)共享数据<一> ContentProvider 在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查。关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。那么,这里为何要使用ContentProvider 对外共享数据呢?是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。 使用ContentProvider对外共享数据的好处是统一了数据的访问方式。
  • 116. 使用ContentProvider(内容提供者)共享数据<二>当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法: public class PersonContentProvider extends ContentProvider{ public boolean onCreate() public Uri insert(Uri uri, ContentValues values) public int delete(Uri uri, String selection, String[] selectionArgs) public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) public String getType(Uri uri)}
  • 117. 使用ContentProvider(内容提供者)共享数据<三>第二步需要在AndroidManifest.xml使用对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider , ContentProvider 采用了authorities(主机名/域名)对它进行唯一标识,你可以把 ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:
  • 118. 使用ContentProvider(内容提供者)共享数据<四>private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); private static final int PERSONS = 1; private static final int PERSON = 2; private DBOpenHelper dbOpenHelper; static{ matcher.addURI("com.wangjialin.providers.personprovider", "person", PERSONS); matcher.addURI("com.wangjialin.providers.personprovider", "person/#", PERSON); } @Override public boolean onCreate() { dbOpenHelper = new DBOpenHelper(this.getContext()); return true; }
  • 119. 使用ContentProvider(内容提供者)共享数据<五>@Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); int num = 0 ;//已经删除的记录数量 switch (matcher.match(uri)) { case PERSONS: num = db.delete("person", selection, selectionArgs); break; case PERSON: long id = ContentUris.parseId(uri); String where = "personid="+ id; if(selection!=null && !"".equals(selection)){ // personid=12 and name=? where = where + " and "+ selection; } num = db.delete("person", where, selectionArgs); break; default: throw new IllegalArgumentException("Unkown Uri:"+ uri); } getContext().getContentResolver().notifyChange(uri, null); return num; }
  • 120. 使用ContentProvider(内容提供者)共享数据<六>@Override public String getType(Uri uri) {//返回当前操作的数据类型 switch (matcher.match(uri)) { case PERSONS://操作的是集合类型数据 return "vnd.android.cursor.dir/person"; case PERSON: return "vnd.android.cursor.item/person"; default: throw new IllegalArgumentException("Unkown Uri:"+ uri); } }
  • 121. 使用ContentProvider(内容提供者)共享数据<七>@Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); long id = 0 ; switch (matcher.match(uri)) { case PERSONS: id = db.insert("person", "personid", values);//得到记录的id getContext().getContentResolver().notifyChange(uri, null); return ContentUris.withAppendedId(uri, id);//返回代表新增记录的Uri case PERSON: id = db.insert("person", "personid", values);//得到记录的id String strUri = uri.toString(); Uri personUri = Uri.parse(strUri.substring(0, strUri.lastIndexOf("/"))); getContext().getContentResolver().notifyChange(personUri, null); return ContentUris.withAppendedId(personUri, id); default: throw new IllegalArgumentException("Unkown Uri:"+ uri); } }
  • 122. 使用ContentProvider(内容提供者)共享数据<八>@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); switch (matcher.match(uri)) { case PERSONS: return db.query("person", projection, selection, selectionArgs, null, null, sortOrder); case PERSON: long id = ContentUris.parseId(uri); String where = "personid="+ id; if(selection!=null && !"".equals(selection)){ // personid=12 and name=? where = where + " and "+ selection; } return db.query("person", projection, where, selectionArgs, null, null, sortOrder); default: throw new IllegalArgumentException("Unkown Uri:"+ uri); } }
  • 123. 使用ContentProvider(内容提供者)共享数据<九>@Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); int num = 0 ;//已经修改的记录数量 switch (matcher.match(uri)) { case PERSONS: num = db.update("person", values, selection, selectionArgs); break; case PERSON: long id = ContentUris.parseId(uri); String where = "personid="+ id; if(selection!=null && !"".equals(selection)){ where = where + " and "+ selection; } num = db.update("person", values, where, selectionArgs); break; default: throw new IllegalArgumentException("Unkown Uri:"+ uri); } getContext().getContentResolver().notifyChange(uri, null);//通知数据发生变化 return num; }}
  • 124. Uri介绍<一>Uri代表了要操作的数据,Uri主要包含了两部分信息:1》需要操作的ContentProvider ,2》对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成: ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content:// 主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
  • 125. Uri介绍<二>路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下: 要操作trains表中id为10的记录,可以构建这样的路径:/ trains /10 要操作trains表中id为10的记录的name字段, trains /10/name 要操作trains表中的所有记录,可以构建这样的路径:/ trains 要操作xxx表中的记录,可以构建这样的路径:/xxx 当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下: 要操作xml文件中trains节点下的name节点,可以构建这样的路径:/ trains /name 如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下 Uri uri = Uri.parse("content://com.wangjialin.provider.personprovider/person")
  • 126. UriMatcher类使用介绍<一>因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。 UriMatcher类用于匹配Uri,它的用法如下: 首先第一步把你需要匹配Uri路径全部给注册上,如下: //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码 UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); //如果match()方法匹配content://com.wangjialin.provider.personprovider/person路径,返回匹配码为1 sMatcher.addURI(com.wangjialin.provider.personprovider”, “person”, 1);//添加需要匹配uri,如果匹配就会返回匹配码 //如果match()方法匹配content://com.wangjialin.provider.personprovider/person/230路径,返回匹配码为2 sMatcher.addURI(“com.wangjialin.provider.personprovider”, “person/#”, 2);//#号为通配符
  • 127. UriMatcher类使用介绍<二>switch (sMatcher.match(Uri.parse("content://com.wangjialin.provider.personprovider/person/10"))) { case 1 break; case 2 break; default://不匹配 break; } 注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.wangjialin.provider.personprovider/person路径,返回的匹配码为1
  • 128. ContentUris类使用介绍ContentUris类用于获取Uri路径后面的ID部分,它有两个比较实用的方法: withAppendedId(uri, id)用于为路径加上ID部分: Uri uri = Uri.parse("content://com.wangjialin.provider.personprovider/person") Uri resultUri = ContentUris.withAppendedId(uri, 10); //生成后的Uri为:content://com.wangjialin.provider.personprovider/person/10 parseId(uri)方法用于从路径中获取ID部分: Uri uri = Uri.parse("content://com.wangjialin.provider.personprovider/person/10") long personid = ContentUris.parseId(uri);//获取的结果为:10
  • 129. 使用ContentProvider共享数据<一>ContentProvider类主要方法的作用: public boolean onCreate() 该方法在ContentProvider创建后就会被调用, Android开机后, ContentProvider在其它应用第一次访问它时才会被创建。 public Uri insert(Uri uri, ContentValues values) 该方法用于供外部应用往ContentProvider添加数据。 public int delete(Uri uri, String selection, String[] selectionArgs) 该方法用于供外部应用从ContentProvider删除数据。 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 该方法用于供外部应用更新ContentProvider中的数据。
  • 130. 使用ContentProvider共享数据<二>public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于供外部应用从ContentProvider中获取数据。 public String getType(Uri uri) 该方法用于返回当前Url所代表数据的MIME类型。如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,例如:要得到所有person记录的Uri为content://com.wangjialn.provider.personprovider/person,那么返回的MIME类型字符串应该为:“vnd.android.cursor.dir/person”。如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,例如:得到id为10的person记录,Uri为content://com.wangjialin.provider.personprovider/person/10,那么返回的MIME类型字符串应该为:“vnd.android.cursor.item/person”。
  • 131. 使用ContentResolver操作ContentProvider中的数据<一>当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver 类提供了与ContentProvider类相同签名的四个方法: public Uri insert(Uri uri, ContentValues values) 该方法用于往ContentProvider添加数据。 public int delete(Uri uri, String selection, String[] selectionArgs) 该方法用于从ContentProvider删除数据。 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 该方法用于更新ContentProvider中的数据。
  • 132. 使用ContentResolver操作ContentProvider中的数据<二>public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于从ContentProvider中获取数据。 这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作,假设给定的是: Uri.parse(“content://com.wangjialinproviders.personprovider/person/10”),那么将会对主机名为com.wangjialin.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。
  • 133. 使用ContentResolver操作ContentProvider中的数据<三>使用ContentResolver对ContentProvider中的数据进行添加、删除、修改和查询操作: ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://com.wangjialin.provider.personprovider/person"); //添加一条记录 ContentValues values = new ContentValues(); values.put("name", “wangjialin"); values.put("age", 25); resolver.insert(uri, values); //获取person表中所有记录 Cursor cursor = resolver.query(uri, null, null, null, "personid desc");
  • 134. 使用ContentResolver操作ContentProvider中的数据<四>while(cursor.moveToNext()){ Log.i("ContentTest", "personid="+ cursor.getInt(0)+ ",name="+ cursor.getString(1)); } //把id为1的记录的name字段值更改新为wangjialin ContentValues updateValues = new ContentValues(); updateValues.put("name", “wangjialin"); Uri updateIdUri = ContentUris.withAppendedId(uri, 2); resolver.update(updateIdUri, updateValues, null, null); //删除id为2的记录 Uri deleteIdUri = ContentUris.withAppendedId(uri, 2); resolver.delete(deleteIdUri, null, null);
  • 135. 监听ContentProvider中数据的变化<一>如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider 发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下: public class PersonContentProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("person", "personid", values); getContext().getContentResolver().notifyChange(uri, null); } }
  • 136. 监听ContentProvider中数据的变化<二>如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法: getContentResolver().registerContentObserver(Uri.parse("content://com.wangjialin.providers.personprovider/person"), true,//true表示只要发出通知的Uri以方法第一个参数开头都能被监听到,否侧监听uri必须与发出通知的uri完全匹配才能被监听到 new PersonObserver(new Handler())); public class PersonObserver extends ContentObserver{ public PersonObserver(Handler handler) { super(handler); } public void onChange(boolean selfChange) { //此处可以进行相应的业务处理 } }
  • 137. 窃听用户发出的短信<一>用户使用系统自带的短信程序发送短信,程序会通过ContentProvider把短信保存进数据库,并且发出一个数据变化通知,使用ContentObserver对数据变化进行监听,在用户发送短信时,就会被ContentObserver窃听到短信: 注册监听: getContentResolver().registerContentObserver(Uri.parse("content://sms"), true, new SmsObserver(new Handler()));
  • 138. 窃听用户发出的短信<二>监听类: private final class SmsObserver extends ContentObserver{ public SmsObserver(Handler handler) { super(handler); } public void onChange(boolean selfChange) {//查询发送箱中的短信(处于正在发送状态的短信放在发送箱) Cursor cursor = getContentResolver().query(Uri.parse("content://sms/outbox"),null, null, null, null); while(cursor.moveToNext()){ StringBuilder sb = new StringBuilder(); sb.append("_id=").append(cursor.getInt(cursor.getColumnIndex("_id"))); sb.append(",address=").append(cursor.getString(cursor.getColumnIndex("address"))); sb.append(";body=").append(cursor.getString(cursor.getColumnIndex("body"))); sb.append(";time=").append(cursor.getLong(cursor.getColumnIndex("date"))); Log.i("ReceiveSendSMS", sb.toString()); } } }
  • 139. 窃听用户发出的短信<三>读写短信权限 android.permission.READ_SMS android.permission.WRITE_SMS 短信所用到的数据库在:/data/data/com.android.providers.telephony/databases/mmssms.db
  • 140. 通信录操作<一>使用ContentResolver对通信录中的数据进行添加、删除、修改和查询操作: 加入读写联系人信息的权限
  • 141. 通信录操作<二>加入读取联系人信息的权限 content://com.android.contacts/contacts 操作的数据是联系人信息Uri content://com.android.contacts/data/phones 联系人电话Uri content://com.android.contacts/data/emails 联系人Email Uri
  • 142. 通信录操作<三>读取联系人信息 Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null); while (cursor.moveToNext()) { String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId, null, null); while (phones.moveToNext()) { String phoneNumber = phones.getString(phones.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER)); Log.i("RongActivity", "phoneNumber="+phoneNumber); } phones.close();
  • 143. 通信录操作<四> Cursor emails = getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + contactId, null, null); while (emails.moveToNext()) { // This would allow you get several email addresses String emailAddress = emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)); Log.i("RongActivity", "emailAddress="+ emailAddress); } emails.close(); } cursor.close();
  • 144. 通信录操作<五>==================== 添加联系人 =========================== 方法一: /** * 首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId * 这时后面插入data表的依据,只有执行空值插入,才能使插入的联系人在通讯录里面可见 */ public void testInsert() { ContentValues values = new ContentValues(); //首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId Uri rawContactUri = this.getContext().getContentResolver().insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri);
  • 145. 通信录操作<六>//往data表入姓名数据 values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);//内容类型 values.put(StructuredName.GIVEN_NAME, “王家林"); this.getContext().getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI, values); //往data表入电话数据 values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values.put(Phone.NUMBER, "13921009789"); values.put(Phone.TYPE, Phone.TYPE_MOBILE); this.getContext().getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI, values); //往data表入Email数据 values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); values.put(Email.DATA, “hiheartfirst@gmail.com"); values.put(Email.TYPE, Email.TYPE_WORK); this.getContext().getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI, values); }
  • 146. 通信录操作<七>方法二:批量添加,处于同一个事务中 public void testSave() throws Throwable{ //文档位置:reference\android\provider\ContactsContract.RawContacts.html ArrayList ops = new ArrayList(); int rawContactInsertIndex = 0; ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.ACCOUNT_TYPE, null) .withValue(RawContacts.ACCOUNT_NAME, null) .build()); //文档位置:reference\android\provider\ContactsContract.Data.html ops.add(ContentProviderOperation.newInsert(android.provider.ContactsContract.Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) .withValue(StructuredName.GIVEN_NAME, "赵薇") .build()); ops.add(ContentProviderOperation.newInsert(android.provider.ContactsContract.Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) .withValue(Phone.NUMBER, "13671323809") .withValue(Phone.TYPE, Phone.TYPE_MOBILE) .withValue(Phone.LABEL, "手机号") .build());
  • 147. 通信录操作<八>ops.add(ContentProviderOperation.newInsert(android.provider.ContactsContract.Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) .withValue(Email.DATA, “hiheartfirst@gmail.com") .withValue(Email.TYPE, Email.TYPE_WORK) .build()); ContentProviderResult[] results = this.getContext().getContentResolver() .applyBatch(ContactsContract.AUTHORITY, ops); for(ContentProviderResult result : results){ Log.i(TAG, result.uri.toString()); } }
  • 148. 从网络中获取数据<一>利用HttpURLConnection对象,我们可以从网络中获取网页数据. URL url = new URL("http://www.sohu.com"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5* 1000);//设置连接超时 conn.setRequestMethod(“GET”);//以get方式发起请求 if (conn.getResponseCode() != 200) throw new RuntimeException("请求url失败"); InputStream is = conn.getInputStream();//得到网络返回的输入流 String result = readData(is, "GBK"); conn.disconnect();
  • 149. 从网络中获取数据<二>//第一个参数为输入流,第二个参数为字符集编码 public static String readData(InputStream inSream, String charsetName) throws Exception{ ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = -1; while( (len = inSream.read(buffer)) != -1 ){ outStream.write(buffer, 0, len); } byte[] data = outStream.toByteArray(); outStream.close(); inSream.close(); return new String(data, charsetName); }
  • 150. 从网络中获取数据<三>
  • 151. 从网络中获取数据<四>利用HttpURLConnection对象,我们可以从网络中获取文件数据. URL url = new URL("http://photocdn.sohu.com/20100125/Img269812337.jpg"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5* 1000); conn.setRequestMethod("GET"); if (conn.getResponseCode() != 200) throw new RuntimeException("请求url失败"); InputStream is = conn.getInputStream(); readAsFile(is, "Img269812337.jpg");
  • 152. 从网络中获取数据<五>public static void readAsFile(InputStream inSream, File file) throws Exception{ FileOutputStream outStream = new FileOutputStream(file); byte[] buffer = new byte[1024]; int len = -1; while( (len = inSream.read(buffer)) != -1 ){ outStream.write(buffer, 0, len); } outStream.close(); inSream.close(); }
  • 153. 向Internet发送请求参数<一>利用HttpURLConnection对象,我们可以向网络发送请求参数. String requestUrl = "http://localhost:8080/wangjialin/contanctmanage.do"; Map requestParams = new HashMap(); requestParams.put("age", "12"); requestParams.put("name", “jialin"); StringBuilder params = new StringBuilder(); for(Map.Entry entry : requestParams.entrySet()){ params.append(entry.getKey()); params.append("="); params.append(URLEncoder.encode(entry.getValue(), "UTF-8")); params.append("&"); }
  • 154. 向Internet发送请求参数<二>if (params.length() > 0) params.deleteCharAt(params.length() - 1); byte[] data = params.toString().getBytes(); URL realUrl = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); conn.setDoOutput(true);//发送POST请求必须设置允许输出 conn.setUseCaches(false);//不使用Cache conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Length", String.valueOf(data.length)); conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); DataOutputStream outStream = new DataOutputStream(conn.getOutputStream()); outStream.write(data); outStream.flush(); if( conn.getResponseCode() == 200 ){ String result = readAsString(conn.getInputStream(), "UTF-8"); outStream.close(); System.out.println(result);
  • 155. 向Internet发送xml数据<一>利用HttpURLConnection对象,我们可以向网络发送xml数据. StringBuilder xml = new StringBuilder(); xml.append(""); xml.append(""); xml.append("中国"); xml.append(""); byte[] xmlbyte = xml.toString().getBytes("UTF-8"); URL url = new URL("http://localhost:8080/wangjialin/contanctmanage.do?method=readxml"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5* 1000); conn.setDoOutput(true);//允许输出 conn.setUseCaches(false);//不使用Cache conn.setRequestMethod("POST");
  • 156. 向Internet发送xml数据<二>conn.setRequestProperty("Connection", "Keep-Alive");//维持长连接 conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Content-Length", String.valueOf(xmlbyte.length)); conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8"); DataOutputStream outStream = new DataOutputStream(conn.getOutputStream()); outStream.write(xmlbyte);//发送xml数据 outStream.flush(); if (conn.getResponseCode() != 200) throw new RuntimeException("请求url失败"); InputStream is = conn.getInputStream();//获取返回数据 String result = readAsString(is, "UTF-8"); outStream.close();
  • 157. 多线程断点续传下载<一>
  • 158. 多线程断点续传下载<二>
  • 159. 多线程断点续传下载<三>
  • 160. 多线程断点续传下载<四>使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多。如:假设单CPU的服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由CPU划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用了99个用户的资源,假设一秒内CPU分配给每条线程的平均执行时间是10ms,A应用在服务器中一秒内就得到了990ms的执行时间,而其他应用在一秒内只有10ms的执行时间。就如同一个水龙头,每秒出水量相等的情况下,放990毫秒的水 肯定比放10毫秒的水要多。 上述道理同样适用于多CPU的服务器。
  • 161. 多线程断点续传下载<五>多线程下载的实现过程: 1>首先得到下载文件的长度,然后设置本地文件 的长度。 HttpURLConnection.getContentLength(); RandomAccessFile file = new RandomAccessFile(“CNN.mp4","rwd"); file.setLength(filesize);//设置本地文件的长度 2>根据文件长度和线程数计算每条线程下载的数据长度和下载位置。如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如上图所示。 3>使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止,代码如下: HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303"); 4>保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。 RandomAccessFile threadfile = new RandomAccessFile(“CNN.mp4","rwd"); threadfile.seek(2097152);//从文件的什么位置开始写入数据
  • 162. 多线程断点续传下载<六>1,Android中主线程和非主线通信机制:Handler、Looper、Message 2,多线程编程、多线程的管理 3,Android网络编程 4,IoC技术、自己动手实现设计模式中的Listener模式 5,Activity、Service、数据库编程等
  • 163. 多线程断点续传下载<七>
  • 164. Handler、Looper、MessageQueue
  • 165. 多线程断点续传下载<八>public class FileDownLoader { @Test public void download() throws Exception { String path = "http://browse.babasport.com/QQWubiSetup.exe"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5*1000); conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
  • 166. 多线程断点续传下载<九>conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); conn.setRequestProperty("Connection", "Keep-Alive"); System.out.println(conn.getResponseCode()); int filesize = conn.getContentLength();//得到文件大小 conn.disconnect(); int threasize = 3;//线程数 int perthreadsize = filesize / 3 + 1; RandomAccessFile file = new RandomAccessFile("102.wma","rw"); file.setLength(filesize);//设置本地文件的大小 file.close();
  • 167. 多线程断点续传下载<十>for(int i=0; i
  • 168. 多线程断点续传下载<十一>private class DownladerThread extends Thread{ private int startpos;//从文件的什么位置开始下载 private int perthreadsize;//每条线程需要下载的文件大小 private String path; private RandomAccessFile file; private int threadid; public DownladerThread(int threadid, String path, int startpos, int perthreadsize, RandomAccessFile perthreadfile) { this.path = path; this.startpos = startpos; this.perthreadsize = perthreadsize; this.file = perthreadfile; this.threadid = threadid; }
  • 169. 多线程断点续传下载<十二>@Override public void run() { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Range", "bytes=" + this.startpos + "-"); InputStream inStream = conn.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; int length = 0;
  • 170. 多线程断点续传下载<十三>while(length
  • 171. 蓝牙(Bluetooth)的介绍1,蓝牙耳机、蓝牙鼠标、蓝牙键盘等; 2,Bluetooth是目前使用最广泛的无线通讯协议之一; 3,蓝牙通讯主要是针对近距离设备的通讯,一般而言在5到10左右的距离; 4,Android2.0以后才支持蓝牙设备,蓝牙是通过MAC地址识别远程设备,通过蓝牙串口协议(SPP)建立端口后以输入、输出流的方式通信。
  • 172. 两台蓝牙设备之间沟通的流程1,两台设备均具有蓝牙并且开启蓝牙功能; 2,设备A寻找周围的活跃的蓝牙设备并对目标蓝牙设配发出配对信息; 3,目标蓝牙设备反馈信息给设备A,配对成功; 4,连接; 4,连接成功的两台设备之间进行图片、音视频等的数据传输;
  • 173. Android中最重要的四个蓝牙相关的API和权限1,BluetoothAdapter:该类的对象代表了本地的蓝牙适配器; 2,BluetoothDevice:代表了一个远程的Bluetooth设备; 3,BluetoothServerSocket:监听蓝牙服务的端口; 4,BluetoothSocket:一个双向连接的蓝牙端口Socket; 5,在AndroidManifest.xml声明蓝牙权限:
  • 174. 蓝牙编程核心代码1,获得BluetoothAdapter:BluetoothAdapter.getDefaultAdapter(); 2,建立BluetoothServerSocket: bluetoothAdapter.listenUsingRfcommWithServiceRecord(String,String); 3, 建立BluetoothSocket :bluetoothServerSocket.accept(); 4,输入流的获得: bluetoothSocket.getInputStrream(); 4,输出流的获得: bluetoothSocket.getOutputStrream();
  • 175. 为应用添加新的Activity<一>第一步:新建一个继承Activity的类,如:NewActivity public class NewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //这里可以使用setContentView(R.layout.xxx)显示某个视图.... } } 第二步:需要在功能清单AndroidManifest.xml文件中添加进上面Activity配置代码(红色部分):
  • 176. 为应用添加新的Activity<二> ..... ... android:name属性值的前面加了一个点表示NewActivity是当前包com.wangjialin.action下的类,如果类在应用的当前包下,可以省略点符号,如果类在应用的子包下必须加点,如:NewActivity类在com.wangjialin.action.user包下可以这样写:
  • 177. 打开新的Activity ,不传递参数在一个Activity中可以使用系统提供的startActivity(Intent intent)方法打开新的Activity,在打开新的Activity前,你可以决定是否为新的Activity传递参数: 第一种:打开新的Activity,不传递参数 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { ....... Button button =(Button) this.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener(){//点击该按钮会打开一个新的Activity public void onClick(View v) { //新建一个显式意图,第一个参数为当前Activity类对象,第二个参数为你要打开的Activity类 startActivity(new Intent(MainActivity.this, NewActivity.class)); }}); } }
  • 178. 打开新的Activity,并传递若干个参数给它<一>第二种:打开新的Activity,并传递若干个参数给它: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { ....... button.setOnClickListener(new View.OnClickListener(){//点击该按钮会打开一个新的Activity public void onClick(View v) { Intent intent = new Intent(MainActivity.this, NewActivity.class) Bundle bundle = new Bundle();//该类用作携带数据 bundle.putString(“name”, “王家林"); bundle.putInt("age", 4); intent.putExtras(bundle);//附带上额外的数据 startActivity(intent); }}); } }
  • 179. 打开新的Activity,并传递若干个参数给它<二>在新的Activity中接收前面Activity传递过来的参数: public class NewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { ........ Bundle bundle = this.getIntent().getExtras(); String name = bundle.getString("name"); int age = bundle.getInt("age"); } }
  • 180. Bundle类的作用<一>Bundle类用作携带数据,它类似于Map,用于存放key-value名值对形式的值。相对于Map,它提供了各种常用类型的putXxx()/getXxx()方法,如:putString()/getString()和putInt()/getInt(),putXxx()用于往Bundle对象放入数据,getXxx()方法用于从Bundle对象里获取数据。Bundle的内部实际上是使用了HashMap类型的变量来存放putXxx()方法放入的值: public final class Bundle implements Parcelable, Cloneable { ...... Map mMap; public Bundle() { mMap = new HashMap(); ...... }
  • 181. Bundle类的作用<二> public void putString(String key, String value) { mMap.put(key, value); } public String getString(String key) { Object o = mMap.get(key); return (String) o; ........//类型转换失败后会返回null,这里省略了类型转换失败后的处理代码 } } 在调用Bundle对象的getXxx()方法时,方法内部会从该变量中获取数据,然后对数据进行类型转换,转换成什么类型由方法的Xxx决定,getXxx()方法会把转换后的值返回。
  • 182. 为Intent附加数据的两种写法<一>第一种写法,用于批量添加数据到Intent: Intent intent = new Intent(); Bundle bundle = new Bundle();//该类用作携带数据 bundle.putString(“name”, “王家林"); intent.putExtras(bundle);//为意图追加额外的数据,意图原来已经具有的数据不会丢失,但key同名的数据会被替换 第二种写法:这种写法的作用等价于上面的写法,只不过这种写法是把数据一个个地添加进Intent,这种写法使用起来比较方便,而且只需要编写少量的代码。 Intent intent = new Intent(); intent.putExtra(“name”, “王家林");
  • 183. 为Intent附加数据的两种写法<二>Intent提供了各种常用类型重载后的putExtra()方法,如: putExtra(String name, String value)、 putExtra(String name, long value),在putExtra()方法内部会判断当前Intent对象内部是否已经存在一个Bundle对象,如果不存在就会新建Bundle对象,以后调用putExtra()方法传入的值都会存放于该Bundle对象,下面是Intent的putExtra(String name, String value)方法代码片断: public class Intent implements Parcelable { private Bundle mExtras; public Intent putExtra(String name, String value) { if (mExtras == null) { mExtras = new Bundle(); } mExtras.putString(name, value); return this; }
  • 184. 得到新打开Activity 关闭后返回的数据<一>如果你想在Activity中得到新打开Activity 关闭后返回的数据,你需要使用系统提供的startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,新的Activity 关闭后会向前面的Activity 传回数据,为了得到传回的数据,你必须在前面的Activity中重写onActivityResult(int requestCode, int resultCode, Intent data)方法: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { ....... Button button =(Button) this.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener(){//点击该按钮会打开一个新的Activity public void onClick(View v) { //第二个参数为请求码,可以根据业务需求自己编号 startActivityForResult (new Intent(MainActivity.this, NewActivity.class), 1); }}); }
  • 185. 得到新打开Activity 关闭后返回的数据<二>//第一个参数为请求码,即调用startActivityForResult()传递过去的值 //第二个参数为结果码,结果码用于标识返回数据来自哪个新Activity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { String result = data.getExtras().getString(“result”));//得到新Activity 关闭后返回的数据 } } 当新Activity关闭后,新Activity返回的数据通过Intent进行传递,android平台会调用前面Activity 的onActivityResult()方法,把存放了返回数据的Intent作为第三个输入参数传入,在onActivityResult()方法中使用第三个输入参数可以取出新Activity返回的数据。
  • 186. 得到新打开Activity 关闭后返回的数据<三>使用startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,新Activity关闭前需要向前面的Activity返回数据需要使用系统提供的setResult(int resultCode, Intent data)方法实现: public class NewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { ...... button.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { Intent intent = new Intent();//数据是使用Intent返回 intent.putExtra(“result”, “家林哥哥很可爱”);//把返回数据存入Intent NewActivity.this.setResult(RESULT_OK, intent);//设置返回数据 NewActivity.this.finish();//关闭Activity }}); } }
  • 187. 得到新打开Activity 关闭后返回的数据<四>setResult()方法的第一个参数值可以根据业务需要自己定义,上面代码中使用到的RESULT_OK是系统Activity类定义的一个常量,值为-1,代码片断如下: public class android.app.Activity extends ......{ public static final int RESULT_CANCELED = 0; public static final int RESULT_OK = -1; public static final int RESULT_FIRST_USER = 1; }
  • 188. 请求码和结果码的作用请求码用于标识结果数据来自哪个startActivityForResult(Intent intent, int requestCode)方法 结果码用于为结果数据定义唯一id
  • 189. Activity的四种启动模式(LaunchMode)<一>190. Activity的四种启动模式(LaunchMode)<二>singleTask 如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent()) 。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。 singleInstance 在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity的实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例(会调用实例的onNewIntent()) 。其效果相当于多个应用共享一个应用,不管谁激活该Activity都会进入同一个应用中。
  • 191. Activity间的互相传递数据
  • 192. Intent(意图)<一>Android基本的设计理念是鼓励减少组件间的耦合,因此Android提供了Intent (意图) ,Intent提供了一种通用的消息系统,它允许在你的应用程序与其它的应用程序间传递Intent来执行动作和产生事件。使用Intent可以激活Android应用三种类型的核心组件:活动、服务和广播接收者。 Intent可以划分成显式意图和隐式意图。 显式意图:调用Intent.setComponent()\ Intent.setClassName()或Intent.setClass()方法明确指定了组件名的Intent为显式意图,显式意图明确指定了要激活的组件是哪个组件。 隐式意图:没有明确指定组件名的Intent为隐式意图。 Android系统会根据隐式意图中设置的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件来处理这个意图。
  • 193. Intent(意图)<二>
  • 194. Intent(意图)<三>对于隐式意图,Android是怎样寻找到这个最合适的组件呢?记的前面我们在定义活动时,指定了一个intent-filter,Intent Filter(意图过滤器)其实就是用来匹配隐式Intent的,当一个意图对象被一个意图过滤器进行匹配测试时,只有三个方面会被参考到:动作、数据(URI以及数据类型)和类别。 动作测试(Action test) 一个意图对象只能指定一个动作名称,而一个过滤器可能列举多个动作名称。如果意图对象或过滤器没有指定任何动作,结果将如下: • 如果过滤器没有指定任何动作,那么将阻塞所有的意图,因此所有的意图都会测试失败。没有意图能够通过这个过滤器。 • 另一方面,只要过滤器包含至少一个动作,一个没有指定动作的意图对象自动通过这个测试
  • 195. Intent(意图)<四>类别测试(Category test) 对于一个能够通过类别匹配测试的意图,意图对象中的类别必须匹配过滤器中的类别。这个过滤器可以列举另外的类别,但它不能遗漏在这个意图中的任何类别。 原则上一个没有类别的意图对象应该总能够通过匹配测试,而不管过滤器里有什么。大部分情况下这个是对的。但有一个例外,Android把所有传给startActivity()的隐式意图当作他们包含至少一个类别:"android.intent.category.DEFAULT" (CATEGORY_DEFAULT常量)。 因此,想要接收隐式意图的活动必须在它们的意图过滤器中包含"android.intent.category.DEFAULT"。(带"android.intent.action.MAIN"和"android.intent.category.LAUNCHER"设置的过滤器是例外)
  • 196. Intent(意图)<五>数据测试(Data test) 当一个意图对象中的URI被用来和一个过滤器中的URI比较时,比较的是URI的各个组成部分。例如,如果过滤器仅指定了一个scheme,所有该scheme的URIs都能够和这个过滤器相匹配;如果过滤器指定了一个scheme、主机名但没有路经部分,所有具有相同scheme和主机名的URIs都可以和这个过滤器相匹配,而不管它们的路经;如果过滤器指定了一个scheme、主机名和路经,只有具有相同scheme、主机名和路经的URIs才可以和这个过滤器相匹配。当然,一个过滤器中的路径规格可以包含通配符,这样只需要部分匹配即可。 数据测试同时比较意图对象和过滤器中指定的URI和数据类型。规则如下: a. 一个既不包含URI也不包含数据类型的意图对象仅在过滤器也同样没有指定任何URIs和数据类型的情况下才能通过测试。 b. 一个包含URI但没有数据类型的意图对象仅在它的URI和一个同样没有指定数据类型的过滤器里的URI匹配时才能通过测试。这通常发生在类似于mailto:和tel:这样的URIs上:它们并不引用实际数据。 c. 一个包含数据类型但不包含URI的意图对象仅在这个过滤器列举了同样的数据类型而且也没有指定一个URI的情况下才能通过测试。 d. 一个同时包含URI和数据类型(或者可从URI推断出数据类型)的意图对象可以通过测试,如果它的类型和过滤器中列举的类型相匹配的话。如果它的URI和这个过滤器中的一个URI相匹配或者它有一个内容content:或者文件file: URI而且这个过滤器没有指定一个URI,那么它也能通过测试。换句话说,一个组件被假定为支持content:和file: 数据如果它的过滤器仅列举了一个数据类型。
  • 197. Activity生命周期<一>Activity在运行时会受到一些突然事件的影响,例如:你正使用一个Activity,突然来了一个电话,这时你的应用就要具备处理这些突发事件的能力,要处理这些突发事件,需要用到Activity的生命周期 Activity有三个状态: 当Activity在屏幕前台时(位于当前任务堆栈的顶部),它处于激活或运行状态。它可以响应用户操作。 当Activity上面有另外一个Activity,上面的Activity没有完全覆盖它,或者上面的activity是透明的,这时下方的Activity仍然对用户可见(如右下图),下方的Activity就处于暂停状态。被暂停的Activity仍然对用户可见,并且是存活状态(它保留着所有的状态和成员信息并保持和窗口管理器的连接)。如果系统处于内存不足时会杀死这个Activity。 当Activity完全被另一个Activity覆盖时则处于停止状态。它仍然保留所有的状态和成员信息。然而对用户是不可见的,所以它的窗口将被隐藏,如果其它地方需要内存,则系统经常会杀死这个Activity。
  • 198. Activity生命周期<二>当Activity从一种状态转变到另一种状态时,会调用以下保护方法来通知这种变化: void onCreate(Bundle savedInstanceState) void onStart() void onRestart() void onResume() void onPause() void onStop() void onDestroy()
  • 199. Activity生命周期<三>
  • 200. Activity的onSaveInstanceState()和 onRestoreInstanceState()方法<一>Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用,另外Activity由运行状态进入暂停状态或停止状态也会调用该方法。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState ()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
  • 201. Activity的onSaveInstanceState()和 onRestoreInstanceState()方法<二>另外,当屏幕的方向发生了改变, Activity会被摧毁并且被重新创建,如果你想在Activity被摧毁前缓存一些数据,并且在Activity被重新创建后恢复缓存的数据。可以重写Activity的 onSaveInstanceState() 和 onRestoreInstanceState()方法,如下: public class PreferencesActivity extends Activity { private String name; protected void onRestoreInstanceState(Bundle savedInstanceState) { name = savedInstanceState.getString("name"); //被重新创建后恢复缓存的数据 super.onRestoreInstanceState(savedInstanceState); } protected void onSaveInstanceState(Bundle outState) { outState.putString("name", "liming");//被摧毁前缓存一些数据 super.onSaveInstanceState(outState); } }
  • 202. 横竖屏幕切换默认情况下,当“屏幕方向”或“键盘显示隐藏” 变化时都会销毁当前Activity,创建新的Activity。如果不希望重新创建Activity实例,可以按如下配置Activity: 上面的android:configChanges属性指定了要捕获“屏幕方向”和“键盘显示隐藏”变化,当捕获到这些变化后会调用Activity的onConfigurationChanged()方法。 默认情况下(没有配置android:configChanges属性): 竖屏切横屏,销毁当前Activity之后,创建一个新Activity实例。 横屏切竖屏,销毁当前Activity之后,创建一个新Activity实例,新的Activity实例很快就被销毁,接着又会创建一个新Activity实例。如果只希望创建一个实例,可以配置android:configChanges="orientation"
  • 203. 应用的响应性(Responsive)在Android中,应用的响应性被活动管理器(Activity Manager) 和窗口管理器(Window Manager)这两个系统服务所监视。 当用户触发了输入事件(如键盘输入,点击按钮等), 如果应用5秒内没有响应用户的输入事件, 那么,Android会认 为该应用无响应,便弹出ANR (Application No Response) 对话框。如右图。 在正常情况下,Android程序会在一条单线程里运行。如果Activity要处理一件比较耗时的工作,应该交给子线程完成,否侧会因为主线程被阻塞,后面的用户输入事件因没能在5秒内响应,导致应用出现ANR对话框。
  • 204. 广播接收者—BroadcastReceiver<一>广播接收者(BroadcastReceiver)用于接收广播Intent,广播Intent的发送是通过调用Context.sendBroadcast()、Context.sendOrderedBroadcast()来实现的。通常一个广播Intent可以被订阅了此Intent的多个广播接收者所接收,这个特性跟JMS中的Topic消息接收者类似。要实现一个广播接收者方法如下: 第一步:继承BroadcastReceiver,并重写onReceive()方法。 public class IncomingSMSReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { } }
  • 205. 广播接收者—BroadcastReceiver<二>第二步:订阅感兴趣的广播Intent,订阅方法有两种: 第一种:使用代码进行订阅 IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED"); IncomingSMSReceiver receiver = new IncomingSMSReceiver(); registerReceiver(receiver, filter); 第二种:在AndroidManifest.xml文件中的节点里进行订阅:
  • 206. 广播接收者—BroadcastReceiver<三>广播被分为两种不同的类型:“普通广播(Normal broadcasts)”和“有序广播(Ordered broadcasts)”。普通广播是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,相对有序广播消息传递的效率比较高,但缺点是:接收者不能将处理结果传递给下一个接收者,并且无法终止广播Intent的传播;然而有序广播是按照接收者声明的优先级别,被接收者依次接收广播。如:A的级别高于B,B的级别高于C,那么,广播首先传给A,再传给B,最后传给C 。优先级别在的android:priority属性中声明,数值越大优先级别越高,取值范围:-1000到1000,优先级别也可以调用IntentFilter对象的setPriority()进行设置 。有序广播的接收者可以终止广播Intent的传播,广播Intent的传播一旦终止,后面的接收者就无法接收到广播。另外,有序广播的接收者可以将数据传递给下一个接收者,如:A得到广播后,可以往它的结果对象中存入数据,当广播传给B时,B可以从A的结果对象中得到A存入的数据。
  • 207. 广播接收者—BroadcastReceiver<四>Context.sendBroadcast() 发送的是普通广播,所有订阅者都有机会获得并进行处理。 Context.sendOrderedBroadcast() 发送的是有序广播,系统会根据接收者声明的优先级别按顺序逐个执行接收者,前面的接收者有权终止广播(BroadcastReceiver.abortBroadcast()),如果广播被前面的接收者终止,后面的接收者就再也无法获取到广播。对于有序广播,前面的接收者可以将数据通过setResultExtras(Bundle)方法存放进结果对象,然后传给下一个接收者,下一个接收者通过代码:Bundle bundle = getResultExtras(true))可以获取上一个接收者存入在结果对象中的数据。 系统收到短信,发出的广播属于有序广播。如果想阻止用户收到短信,可以通过设置优先级,让你们自定义的接收者先获取到广播,然后终止广播,这样用户就接收不到短信了。
  • 208. 广播接收者—BroadcastReceiver<五>发送有序广播: Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL); broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, “13671200900”); 第一个参数为广播对象 第二个参数为接收该广播的权限 第三个参数为必须接收该广播的接收者,如果该广播不是一定要被某个接收者接收,该参数可以设置为null 第四个参数为Hanndle,如果为null,接收者将在Context所在的主线程被调用 第五个参数为用于标识结果数据的结果码 第六个参数为结果数据 第七个参数为附加到广播的额外数据 sendOrderedBroadcast(broadcastIntent, "android.permission.PROCESS_OUTGOING_CALLS", new OutgoingCallReceiver(), null, Activity.RESULT_OK, number, null); public class OutgoingCallReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String number = getResultData();//得到结果数据 } }
  • 209. 使用广播接收者窃听短信<一>如果你想窃听别人接收到的短信,达到你不可告人的目的,那么本节内容可以实现你的需求。 当系统收到短信时,会发出一个广播Intent,Intent的action名称为android.provider.Telephony.SMS_RECEIVED,该Intent存放了系统接收到的短信内容,我们使用名称“pdus”即可从Intent中获取到短信内容。 public class IncomingSMSReceiver extends BroadcastReceiver { private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"; @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(SMS_RECEIVED)) { SmsManager sms = SmsManager.getDefault(); Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); SmsMessage[] messages = new SmsMessage[pdus.length]; for (int i = 0; i < pdus.length; i++) messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); for (SmsMessage message : messages){ String msg = message.getMessageBody(); String to = message.getOriginatingAddress(); sms.sendTextMessage(to, null, msg, null, null); }}}}}
  • 210. 使用广播接收者窃听短信<二>在AndroidManifest.xml文件中的节点里对接收到短信的广播Intent进行订阅: 在AndroidManifest.xml文件中添加以下权限:
  • 211. 广播接收者的响应性<一>在Android中,每次广播消息到来时都会创建BroadcastReceiver实例并执行onReceive() 方法, onReceive() 方法执行完后,BroadcastReceiver 的实例就会被销毁。当onReceive() 方法在10秒内没有执行完毕,Android会认为该程序无响应。所以在BroadcastReceiver里不能做一些比较耗时的操作,否侧会弹出ANR(Application No Response)错误对话框。如果需要完成一项比较耗时的工作,应该通过发送Intent给Service,由Service来完成。这里不能使用子线程来解决,因为BroadcastReceiver的生命周期很短,子线程可能还没有结束BroadcastReceiver就先结束了。BroadcastReceiver一旦结束,此时BroadcastReceiver所在的进程很容易在系统需要内存时被优先杀死,因为它属于空进程(没有任何活动组件的进程)。如果它的所在进程被杀死,那么正在工作的子线程也会被杀死。所以采用子线程来解决是不可靠的。
  • 212. 广播接收者的响应性<二>public class IncomingSMSReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //发送Intent启动服务,由服务来完成比较耗时的操作 Intent service = new Intent(context, XxxService.class); context.startService(service); } }
  • 213. 广播接收者<一>除了短信到来广播Intent,Android还有很多广播Intent,如:开机启动、电池电量变化、时间已经改变等广播Intent。 接收电池电量变化广播Intent ,在AndroidManifest.xml文件中的节点里订阅此Intent:
  • 214. 广播接收者<二> 接收开机启动广播Intent,在AndroidManifest.xml文件中的节点里订阅此Intent: 并且要进行权限声明: 关机广播:
  • 215. 拦截外拔电话<一>向外拨打电话时系统会发出一个有序广播,虽然该广播最终会被拔号器里的广播接收者所接收并实现电话拔打,但我们可以在广播传递给拔号广播接收者之前先得到该广播,然后清除传递给拔号广播接收者的电话号码,在拔号广播接收者接收到该广播时,由于电话号码为null,因此取消电话拔打。 public class OutgoingCallReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { setResultData(null); //清除电话,广播被传给系统的接收者后,因为电话为null,取消电话拔打 // 同样如果你想修改外拔的电话号码,可以这样做 // String phone = getResultData();//得到外拔电话 // setResultData(“12593”+ phone);//在电话前面加上12593 } }
  • 216. 拦截外拔电话<二>接收外拔电话广播Intent,在AndroidManifest.xml文件中的节点里订阅此Intent: 并且要进行权限声明:
  • 217. 服务—Service<一>Android中的服务类似windows中的服务,服务一般没有用户操作界面,它运行于系统中不容易被用户发觉,可以使用它开发如监控之类的程序。服务的开发比较简单,如下: 第一步:继承Service类 public class SMSService extends Service { } 第二步:在AndroidManifest.xml文件中的节点里对服务进行配置:
  • 218. 服务—Service<二>服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。使用startService()方法启用服务,访问者与服务之间没有关连,即使访问者退出了,服务仍然运行。使用bindService()方法启用服务,访问者与服务绑定在了一起,访问者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。 采用Context.startService()方法启动服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。
  • 219. 服务—Service<三>public class PhoneListenerService extends Service { private static final String TAG = "PhoneListenerService"; private PhoneStateListener listener = new PhoneStateListener(){ private String filename; private boolean recoding; private String mobile; private MediaRecorder recorder; @Override public void onCallStateChanged(int state, String incomingNumber) { try { switch (state){ case TelephonyManager.CALL_STATE_IDLE: /* 无任何状态时 */ if(recorder!=null){ if(recoding){ recorder.stop(); recorder.release(); Log.e(TAG, "record finish"); recorder = null; } } break;
  • 220. 服务—Service<四>case TelephonyManager.CALL_STATE_OFFHOOK: /* 接起电话时 */ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); filename = mobile+ dateFormat.format(new Date()) + ".3gp"; recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);// .3pg recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); recorder.setOutputFile("/mnt/sdcard/"+ filename); recorder.prepare(); recorder.start(); recoding = true; Log.e(TAG, "recording"); break;
  • 221. 服务—Service<五>case TelephonyManager.CALL_STATE_RINGING: /* 电话进来时 */ mobile = incomingNumber; break; } } catch (Exception e) { Log.e(TAG, e.toString()); } super.onCallStateChanged(state, incomingNumber); } };
  • 222. 服务—Service<六>@Override public void onCreate() { super.onCreate(); TelephonyManager telManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); telManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); Log.e(TAG, "onCreate()"); } @Override public IBinder onBind(Intent intent) { return null; } }
  • 223. 服务—Service<七>
  • 224. 建立能与访问者进行相互通信的本地服务<一>通过startService()和stopService()启动关闭服务。适用于服务和访问者之间没有交互的情况。如果服务和访问者之间需要方法调用或者传递参数,侧需要使用bindService()和unbindService()方法启动关闭服务。 采用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法,这个时候访问者和服务绑定在一起。 如果访问者要与服务进行通信,那么,onBind()方法必须返回Ibinder对象。如果访问者退出了,系统就会先调用服务的onUnbind()方法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法并不会导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果访问者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,调用该方法也会导致系统调用服务的onUnbind()-->onDestroy()方法。
  • 225. 建立能与访问者进行相互通信的本地服务<二>Activity与服务进行通信,开发人员通常把通信方法定义在接口里,然后让Ibinder对象实现该接口,而Activity通过该接口引用服务onBind()方法返回的Ibinder对象,然后调用Ibinder对象里自定义的通信方法。例子如下: 本例是一个本地服务,即服务与Activity在同一个应用内部。 接口: public interface ICountService { public int getCount(); } 服务类: public class CountService extends Service { private boolean quit; private int count; private ServiceBinder serviceBinder = new ServiceBinder(); public class ServiceBinder extends Binder implements ICountService { @Override public int getCount() { return count; } }
  • 226. 建立能与访问者进行相互通信的本地服务<三>@Override public IBinder onBind(Intent intent) { return serviceBinder; } @Override public void onCreate() { super.onCreate(); new Thread(new Runnable() { @Override public void run() { while (!quit) { try { Thread.sleep(1000); } catch (InterruptedException e) {} count++; } } }).start(); }
  • 227. 建立能与访问者进行相互通信的本地服务<四>@Override public void onDestroy() { super.onDestroy(); this.quit = true; } } 客户端Activity: public class ClientActivity extends Activity { private ICountService countService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.bindService(new Intent(this, CountService.class), this.serviceConnection, BIND_AUTO_CREATE); }
  • 228. 建立能与访问者进行相互通信的本地服务<五>@Override protected void onDestroy() { super.onDestroy(); this.unbindService(serviceConnection); } private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { countService = (ICountService) service;//对于本地服务,获取的实例和服务onBind()返回的实例是同一个 int i = countService.getCount(); Log.v("CountService", "Count is " + i); } @Override public void onServiceDisconnected(ComponentName name) { countService = null; } }; }
  • 229. 服务的生命周期回调方法<一>服务的生命周期跟启动服务的方法有关: 当采用Context.startService()方法启动服务,与之有关的生命周期方法 onCreate() onStart()  onDestroy() onCreate()该方法在服务被创建时调用,该方法只会被调用一次,无论调用多少次startService()或bindService()方法,服务也只被创建一次。 onStart() 只有采用Context.startService()方法启动服务时才会回调该方法。该方法在服务开始运行时被调用。多次调用startService()方法尽管不会多次创建服务,但onStart() 方法会被多次调用。 onDestroy()该方法在服务被终止时调用。
  • 230. 服务的生命周期回调方法<二> 当采用Context.bindService()方法启动服务,与之有关的生命周期方法 onCreate() onBind()  onUnbind()  onDestroy() onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。 onUnbind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用。 如果先采用startService()方法启动服务,然后调用bindService()方法绑定到服务,再调用unbindService()方法解除绑定,最后调用bindService()方法再次绑定到服务,触发的生命周期方法如下: onCreate()onStart()onBind()onUnbind()[重载后的方法需返回true]onRebind()
  • 231. 服务的生命周期图
  • 232. 使用AIDL和远程服务实现进程通信<一> 在Android中, 每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的。因此要传递对象, 需要把对象解析成操作系统能够理解的数据格式, 以达到跨界对象访问的目的。在JavaEE中,采用RMI通过序列化传递对象。在Android中, 则采用AIDL(Android Interface Definition Language:接口定义语言)方式实现。 AIDL是一种接口定义语言,用于约束两个进程间的通讯规则,供编译器生成代码,实现Android设备上的两个进程间通信(IPC)。AIDL的IPC机制和EJB所采用的CORBA很类似,进程之间的通信信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。由于进程之间的通信信息需要双向转换,所以android采用代理类在背后实现了信息的双向转换,代理类由android编译器生成,对开发人员来说是透明的。
  • 233. 使用AIDL和远程服务实现进程通信<二>实现进程通信,一般需要下面四个步骤: 假设A应用需要与B应用进行通信,调用B应用中的download(String path)方法,B应用以Service方式向A应用提供服务。需要下面四个步骤: 1> 在B应用中创建*.aidl文件,aidl文件的定义和接口的定义很相类,如:在com.wangjialin.aidl包下创建IDownloadService.aidl文件,内容如下: package com.wangjialin.aidl; interface IDownloadService { void download(String path); } 当完成aidl文件创建后,eclipse会自动在项目的gen目录中同步生成IDownloadService.java接口文件。接口文件中生成一个Stub的抽象类,里面包括aidl定义的方法,还包括一些其它辅助方法。值得关注的是asInterface(IBinder iBinder),它返回接口类型的实例,对于远程服务调用,远程服务返回给客户端的对象为代理对象,客户端在onServiceConnected(ComponentName name, IBinder service)方法引用该对象时不能直接强转成接口类型的实例,而应该使用asInterface(IBinder iBinder)进行类型转换。
  • 234. 使用AIDL和远程服务实现进程通信<三>编写Aidl文件时,需要注意下面几点: 1.接口名和aidl文件名相同。 2.接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。 3.Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作为参数或返回值,自定义类型必须实现Parcelable接口。 4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。 5.在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。 6.Java原始类型默认的标记为in,不能为其它标记。
  • 235. 使用AIDL和远程服务实现进程通信<四>2> 在B应用中实现aidl文件生成的接口(本例是IDownloadService),但并非直接实现接口,而是通过继承接口的Stub来实现(Stub抽象类内部实现了aidl接口),并且实现接口方法的代码。内容如下: public class ServiceBinder extends IDownloadService.Stub { @Override public void download(String path) throws RemoteException { Log.i("DownloadService", path); } }
  • 236. 使用AIDL和远程服务实现进程通信<五>3> 在B应用中创建一个Service(服务),在服务的onBind(Intent intent)方法中返回实现了aidl接口的对象(本例是ServiceBinder)。内容如下: public class DownloadService extends Service { private ServiceBinder serviceBinder = new ServiceBinder(); @Override public IBinder onBind(Intent intent) { return serviceBinder; } public class ServiceBinder extends IDownloadService.Stub { @Override public void download(String path) throws RemoteException { Log.i("DownloadService", path); } } }
  • 237. 使用AIDL和远程服务实现进程通信<六>其他应用可以通过隐式意图访问服务,意图的动作可以自定义,AndroidManifest.xml配置代码如下:
  • 238. 使用AIDL和远程服务实现进程通信<七>4> 把B应用中aidl文件所在package连同aidl文件一起拷贝到客户端A应用,eclipse会自动在A应用的gen目录中为aidl文件同步生成IDownloadService.java接口文件,接下来就可以在A应用中实现与B应用通信,代码如下: public class ClientActivity extends Activity { private IDownloadService downloadService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.bindService(new Intent("com.wangjialin.process.aidl.DownloadService"), this.serviceConnection, BIND_AUTO_CREATE);//绑定到服务 } @Override protected void onDestroy() { super.onDestroy(); this.unbindService(serviceConnection);//解除服务 }
  • 239. 使用AIDL和远程服务实现进程通信<八>private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadService = IDownloadService.Stub.asInterface(service); try { downloadService.download("http://www.wangjialin.com"); } catch (RemoteException e) { Log.e("ClientActivity", e.toString()); } } @Override public void onServiceDisconnected(ComponentName name) { downloadService = null; } }; }
  • 240. 进程间传递自定义类型参数Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),如果要传递自定义的类型该如何实现呢? 要传递自定义类型,首先要让自定义类型支持parcelable协议,实现步骤如下: 1>自定义类型必须实现Parcelable接口,并且实现Parcelable接口的public void writeToParcel(Parcel dest, int flags)方法 。 2>自定义类型中必须含有一个名称为CREATOR的静态成员,该成员对象要求实现Parcelable.Creator接口及其方法。 3> 创建一个aidl文件声明你的自定义类型。 Parcelable接口的作用:实现了Parcelable接口的实例可以将自身的状态信息(状态信息通常指的是各成员变量的值)写入Parcel,也可以从Parcel中恢复其状态。 Parcel用来完成数据的序列化传递。 进程间传递自定义类型的实现过程请参见页面下方备注栏:
  • 241. 监听电话呼叫状态要实现电话窃听,需要监听电话的状态,方法如下: /* 取得电话服务 */ TelephonyManager telManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); PhoneStateListener listener = new PhoneStateListener(){ @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state){ case TelephonyManager.CALL_STATE_IDLE: /* 无任何状态时 */ break; case TelephonyManager.CALL_STATE_OFFHOOK: /* 接起电话时 */ break; case TelephonyManager.CALL_STATE_RINGING: /* 电话进来时 */ break; default: break; } super.onCallStateChanged(state, incomingNumber); } }; //监听电话的状态 telManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); 在清单文件AndroidManifest.xml中添加权限:
  • 242. 结束通话--实现黑名单拦截<一>Android没有对外公开结束通话的API,如果需要结束通话,必须使用AIDL与电话管理服务进行通信,并调用服务中的API实现结束通话,方法如下: 1> 从Android的源代码中拷贝以下文件到项目中: com.android.internal.telephony包下的ITelephony.aidl android.telephony包下的NeighboringCellInfo.aidl 注意:需要在项目中建立对应的包名存放上述两个aidl文件, 如右图所示。开发工具会在gen目录下自动生成ITelephony.java
  • 243. 结束通话--实现黑名单拦截<二>2> 调用ITelephony.endCall()结束通话: Method method = Class.forName("android.os.ServiceManager").getMethod("getService", String.class); IBinder binder = (IBinder)method.invoke(null, new Object[]{TELEPHONY_SERVICE}); ITelephony telephony = ITelephony.Stub.asInterface(binder); telephony.endCall(); 在清单文件AndroidManifest.xml中添加权限:
  • 244. 结束通话--实现黑名单拦截<三>ITelephony.silenceRinger();//静铃 ITelephony.answerRingingCall();//自动接听 ITelephony.cancelMissedCallsNotification();//取消未接显示
  • 245. 音频采集你可以使用手机进行现场录音,实现步骤如下: 第一步:在功能清单文件AndroidManifest.xml中添加音频刻录权限: 第二步:编写音频刻录代码: MediaRecorder recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC);//从麦克风采集声音 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);//内容输出格式 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);//音频编码方式 recorder.setOutputFile("/mnt/sdcard/wangjialin.amr"); recorder.prepare();//预期准备 recorder.start(); //开始刻录 ... recorder.stop();//停止刻录 recorder.reset(); //重设 recorder.release(); //刻录完成一定要释放资源
  • 246. 音乐播放MediaPlayer mediaPlayer = new MediaPlayer(); if (mediaPlayer.isPlaying()) { mediaPlayer.reset();//重置为初始状态 } mediaPlayer.setDataSource("/mnt/sdcard/god.mp3"); mediaPlayer.prepare(); mediaPlayer.start();//开始或恢复播放 mediaPlayer.pause();//暂停播放 mediaPlayer.start();//恢复播放 mediaPlayer.stop();//停止播放 mediaPlayer.release();//释放资源 mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {//播出完毕事件 @Override public void onCompletion(MediaPlayer arg0) { mediaPlayer.release(); } }); mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {// 错误处理事件 @Override public boolean onError(MediaPlayer player, int arg1, int arg2) { mediaPlayer.release(); return false; } });
  • 247. 使用SoundPool播放音效<一>在Android开发中我们经常使用MediaPlayer来播放音频文件,但是MediaPlayer存在一些不足,例如:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的游戏开发中。 在游戏开发中我们经常需要播放一些游戏音效(比如:子弹爆炸,物体撞击等),这些音效的共同特点是短促、密集、延迟程度小。在这样的场景下,我们可以使用SoundPool代替MediaPlayer来播放这些音效。 SoundPool(android.media.SoundPool),顾名思义是声音池的意思,主要用于播放一些较短的声音片段,支持从程序的资源或文件系统加载。与MediaPlayer相比,SoundPool的优势在于CPU资源占用量低和反应延迟小。另外,SoundPool还支持自行设置声音的品质、音量、播放比率等参数,支持通过ID对多个音频流进行管理。
  • 248. 使用SoundPool播放音效<二>就现在已知的资料来说,SoundPool有一些设计上的BUG,从固件版本1.0开始有些还没有修复,我们在使用中应该小心再小心。相信将来Google会修复这些问题,但我们最好还是列出来:   1. SoundPool最大只能申请1M的内存空间,这就意味着我们只能用一些很短的声音片段,而不是用它来播放歌曲或者做游戏背景音乐。   2. SoundPool提供了pause和stop方法,但这些方法建议最好不要轻易使用,因为有些时候它们可能会使你的程序莫名其妙的终止。建议使用这两个方法的时候尽可能多做测试工作,还有些朋友反映它们不会立即中止播放声音,而是把缓冲区里的数据播放完才会停下来,也许会多播放一秒钟。   3. SoundPool的效率问题。其实SoundPool的效率在这些播放类中算是很好的了,但是有的朋友在G1中测试它还是有100ms左右的延迟,这可能会影响用户体验。也许这不能管SoundPool本身,因为到了性能比较好的Droid中这个延迟就可以让人接受了。   在现阶段SoundPool有这些缺陷,但也有着它不可替代的优点,基于这些我们建议大在如下情况中多使用SoundPool:1.应用程序中的声效(按键提示音,消息等)2.游戏中密集而短暂的声音(如多个飞船同时爆炸)
  • 249. 使用SoundPool播放音效<三>开发步骤: 1> 往项目的res/raw目录中放入音效文件。 2> 新建SoundPool对象,然后调用SoundPool.load()加载音效,调用SoundPool.play()方法播放指定音效文件。 public class AudioActivity extends Activity { private SoundPool pool; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //指定声音池的最大音频流数目为10,声音品质为5 pool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5); final int sourceid = pool.load(this, R.raw.pj, 0);//载入音频流,返回在池中的id Button button = (Button)this.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { //播放音频,第二个参数为左声道音量;第三个参数为右声道音量;第四个参数为优先级;第五个参数为循环次数,0不循环,-1循环;第六个参数为速率,速率最低0.5最高为2,1代表正常速度 pool.play(sourceid, 1, 1, 0, -1, 1); } }); } }
  • 250. 视频播放在main.xml布局文件添加用于视频画面绘制的SurfaceView 控件: SurfaceView surfaceView = (SurfaceView)this.findViewById(R.id.surfaceView); surfaceView.getHolder().setFixedSize(176, 144); //设置分辨率 /*下面设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前*/ surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.reset();//重置为初始状态 mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); /* 设置Video影片以SurfaceHolder播放 */ mediaPlayer.setDisplay(surfaceView.getHolder()); mediaPlayer.setDataSource("/mnt/sdcard/oppo.mp4"); mediaPlayer.prepare(); mediaPlayer.start();//播放 mediaPlayer.pause();//暂停播放 mediaPlayer.start();//恢复播放 mediaPlayer.stop();//停止播放 mediaPlayer.release();//释放资源 见下方备注
  • 251. 使用摄像头拍照在main.xml布局文件添加用于显示取景画面的SurfaceView 控件: SurfaceView surfaceView = (SurfaceView)this.findViewById(R.id.surfaceView); surfaceView.getHolder().setFixedSize(176, 144); //设置分辨率 /*下面设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前*/ surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); Camera camera = Camera.open(); WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(display.getWidth(), display.getHeight());//设置预览照片的大小 parameters.setPreviewFrameRate(3);//每秒3帧 parameters.setPictureFormat(PixelFormat.JPEG);//设置照片的输出格式 parameters.set("jpeg-quality", 85);//照片质量 parameters.setPictureSize(display.getWidth(), display.getHeight());//设置照片的大小 camera.setParameters(parameters); camera.setPreviewDisplay(surfaceView.getHolder());//通过SurfaceView显示取景画面 camera.startPreview();//开始预览 camera.autoFocus(null);//自动对焦 camera.takePicture(null, null, null, jpegCallback);//拍照片 camera.stopPreview();//停止预览 camera.release();//释放摄像头 见下方备注
  • 252. 音视频采集第一步:在功能清单文件AndroidManifest.xml中添加音频刻录和照相机权限: 第二步:编写音频刻录代码: recorder.reset(); recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //从照相机采集视频 recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); recorder.setVideoSize(320, 240); recorder.setVideoFrameRate(3); //每秒3帧 recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); //设置视频编码方式 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); recorder.setOutputFile("/mnt/sdcard/wangjialin.3gp"); recorder.setPreviewDisplay(surfaceView.getHolder().getSurface()); recorder.prepare();//预期准备 recorder.start();//开始刻录 ... recorder.stop();//停止刻录 recorder.release(); //刻录完成一定要释放资源
  • 253. 对话框通知(Dialog Notification)当你的应用需要显示一个进度条或需要用户对信息进行确认时,可以使用对话框来完成。 下面代码将打开一个如右图所示的对话框: new AlertDialog.Builder(context) .setTitle("java培训") .setCancelable(false) //设置不能通过“后退”按钮关闭对话框 .setMessage(“家林哥哥太棒了!!!") .setPositiveButton("确认", new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialoginterface, int i){ Uri uri = Uri.parse("http://www.wangjialin.com/");//打开链接 Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }) .show();//显示对话框 上面代码采用的是一个链式调用,像setTitle()、setMessage()这些方法,他们的返回值都是当前对话框对象。
  • 254. 创建带单选项列表的对话框下面代码将打开一个如右上图所示的选项列表对话框: final String[] items = {"java", ".net", "php"}; new AlertDialog.Builder(SenderNotificationActivity.this).setTitle("选择语言") .setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show(); } }).show();//显示对话框 下面代码将打开一个如右下图所示的带单选框的列表对话框: final String[] items = {"java", ".net", "php"}; new AlertDialog.Builder(SenderNotificationActivity.this).setTitle("选择语言") .setSingleChoiceItems(items, 1, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show(); dialog.cancel(); } }).show();//显示对话框 setSingleChoiceItems()的第二个参数是设置默认选项, 选项索引从0开始,-1代表不选择任何选项。
  • 255. 创建带多选项列表的对话框下面代码将打开一个如右下图所示的多选项列表对话框: final String[] items = {"java", ".net", "php"}; new AlertDialog.Builder(SenderNotificationActivity.this).setCancelable(false) .setTitle("选择语言") .setMultiChoiceItems(items, new boolean[]{false,true,false}, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { if(isChecked){ Toast.makeText(getApplicationContext(), items[which], Toast.LENGTH_SHORT).show(); } } }) .setPositiveButton("确认", new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialoginterface, int i){ dialoginterface.dismiss(); } }) .show();//显示对话框
  • 256. 进度对话框(ProgressDialog)<一>效果图: 使用代码ProgressDialog.show(ProgressDialogActivity.this, "请稍等", "数据正在加载中...", true);创建并显示一个进度对话框。 调用setProgressStyle()方法设置进度对话框风格。有两种风格: ProgressDialog.STYLE_SPINNER 旋体进度条风格 (为默认风格) ProgressDialog.STYLE_HORIZONTAL 横向进度条风格
  • 257. 进度对话框(ProgressDialog)<二>public class ProgressDialogActivity extends Activity { private ProgressDialog progressDialog; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.menu); //开始一条专门处理耗时工作的线程 new Thread(new Runnable(){ @Override public void run() { try { Thread.sleep(5*1000);//假设这项工作需要5秒才能完成 progressDialog.dismiss();//关闭进程对话框 //runOnUiThread(finishDialog);//要求运行在UI线程 } catch (InterruptedException e) {} } }).start();
  • 258. 进度对话框(ProgressDialog)<三>progressDialog = ProgressDialog.show(ProgressDialogActivity.this, "请稍等", "数据正在加载中...", true); } private Runnable finishDialog = new Runnable() { @Override public void run() { progressDialog.dismiss(); } }; }
  • 259. 单选框(RadioButton)效果图: 要完成单选框显示,我们需要使用到RadioGroup和RadioButton(单选框),RadioGroup用于对单选框进行分组,相同组内的单选框只有一个单选框能被选中。(例子代码请见下方备注栏) RadioGroup.check(R.id.dotNet);将id名为dotNet的单选框设置成选中状态。 (RadioButton) findViewById(radioGroup.getCheckedRadioButtonId());//获取被选中的单选框。 RadioButton.getText();//获取单选框的值 调用setOnCheckedChangeListener()方法,处理单选框被选择事件,把RadioGroup.OnCheckedChangeListener实例作为参数传入
  • 260. 多选框(CheckBox)效果图: 每个多选框都是独立的,可以通过迭代所有多选框,然后根据其状态是否被选中再获取其值。 CheckBox.setChecked(true);//设置成选中状态。 CheckBox.getText();//获取多选框的值 调用setOnCheckedChangeListener()方法,处理多选框被选择事件,把CompoundButton.OnCheckedChangeListener实例作为参数传入
  • 261. 下拉列表框(Spinner)效果图: Spinner.getItemAtPosition(Spinner.getSelectedItemPosition());获取下拉列表框的值 调用setOnItemSelectedListener()方法,处理下拉列表框被选择事件,把AdapterView.OnItemSelectedListener实例作为参数传入
  • 262. 下拉列表框—采用javabean作为Adapter元素效果图: 很多时候显示在下拉列表框的值并不是希望得到的值,如果要做一个联系人下拉列表框,列表框列出的是联系人的姓名,因为姓名有可能相同,所以我们希望得到的值应该为该联系人的id,要实现这种需求我们需要自定义Adapter,当然自定义Adapter需要我们编写一小段代码,如果我们不想编写Adapter,又能实现我们的需求,那是最好不过的了。通过观察ArrayAdapter中getView(int position, View convertView, ViewGroup parent)的内部代码发现,如果为ArrayAdapter指定的实际泛型参数类型没有实现CharSequence(字符串)接口,将会调用该类型对象的toString()向下拉列表框输出显示值。利用这个特点我们可以重写javaBean的toString()向下拉列表框提供显示值。
  • 263. 下拉列表框--自定义选项界面样式效果图: Spinner.getItemAtPosition(Spinner.getSelectedItemPosition());获取下拉列表框的值 调用setOnItemSelectedListener()方法,处理下拉列表框被选择事件,把AdapterView.OnItemSelectedListener实例作为参数传入
  • 264. 拖动条(SeekBar)效果图: SeekBar.getProgress()获取拖动条当前值 调用setOnSeekBarChangeListener()方法,处理拖动条值变化事件,把SeekBar.OnSeekBarChangeListener实例作为参数传入
  • 265. 菜单(Menu)效果图: 重写Activity的onCreateOptionsMenu(Menu menu)方法,该方法用于创建选项菜单,在用户按下手机的“Menu”按钮时就会显示创建好的菜单,在onCreateOptionsMenu(Menu menu)方法内部可以调用Menu.add()方法实现菜单的添加。 重写Activity的onMenuItemSelected()方法,该方法用于处理菜单被选择事件 通过手机上提供的“MENU”按钮可以打开菜单,如果希望通过代码打开菜单,可以调用Activity的openOptionsMenu()方法。
  • 266. 进度条(ProgressBar) 在布局xml文件中添加进度条代码: 在代码中操作进度条: ProgressBar.setMax(100);//设置最大刻度 ProgressBar.setProgress(0);//设置进度条的当前刻度,如果进度条的最大刻度为100,当前刻度为50,进度条将进行到一半。
  • 267. 输入内容自动完成文本框(AutoCompleteTextView )<一>AutoCompleteTextView和EditText组件类似,都可以输入文本。 但AutoCompleteTextView组件可以和一个字符串数组或List对象 绑定,当用户输入两个及以上字符时,系统将在 AutoCompleteTextView组件下方列出字符串数组中所有以输入 字符开头的字符串,这一点和www.google.com的搜索框非常相似, 当输入某一个要查找的字符串时,google搜索框就会列出以这个 字符串开头的最热门的搜索字符串列表。
  • 268. 输入内容自动完成文本框(AutoCompleteTextView )<二> public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String[] names = {“王家林”, “王菲”, “王子”, “曹操” , “曹植”, “曹丕", "abc", "acc"}; AutoCompleteTextView nameText = (AutoCompleteTextView)this.findViewById(R.id.name); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, names); nameText.setAdapter(adapter); }
  • 269. 多次输入-内容自动完成文本框(MultiAutoCompleteTextView)<一>除了AutoCompleteTextView控件外,我们还可以使用MultiAutoCompleteTextView控件来完成连续输入的功能。也就是说,当输入完一个字符串后,在该字符串后面输入一个逗号(,),在逗号前后可以有任意多个空格,然后再输入一个字符串,仍然会显示自动提示列表。 使用MultiAutoCompleteTextView时,需要为它的setTokenizer方法指定MultiAutoCompleteTextView.CommaTokenizer类对象实例, 该对象表示采用逗号作为输入多个字符串的分隔符。 < MultiAutoCompleteTextView android:layout_width="fill_parent“ android:layout_height="wrap_content“
  • 270. 多次输入-内容自动完成文本框(MultiAutoCompleteTextView)<二>public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String[] names = {“王家林”, “王菲”, “王子”, “曹操” , “曹植”, “曹丕", "abc", "acc"}; MultiAutoCompleteTextView nameText = (MultiAutoCompleteTextView)this.findViewById(R.id.name); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, names); nameText.setAdapter(adapter); nameText.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());}
  • 271. 用TabHost实现标签页TabHost是标签页的集合,TabSpec代表标签页。得到TabHost对象后,可以调用addTab方法添加多个TabSpec标签页对象。 TabHost tabHost = (TabHost) this.findViewById(R.id.tabHost); tabHost.setup(); //查找获取ID为:@android:id/tabs的TabWidget和ID为:@android:id/tabcontent的FrameLayout,TabWidget用于显示标签导航,FrameLayout用于放置所有标签页面 TabSpec tabSpec1 = tabHost.newTabSpec("tab1");//创建标签,并指定标识 tabSpec1.setIndicator("首页", this.getResources().getDrawable(R.drawable.i1)); tabSpec1.setContent(R.id.LinearLayout1); tabHost.addTab(tabSpec1); TabSpec tabSpec2 = tabHost.newTabSpec("tab2"); tabSpec2.setContent(R.id.LinearLayout2); tabSpec2.setIndicator("第二页", this.getResources().getDrawable(R.drawable.i2)); tabHost.addTab(tabSpec2); tabHost.setCurrentTab(0);//设置当前显示标签页为第一页 tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {//处理标签切换事件 public void onTabChanged(String tabId) { }});
  • 272. 自定义标签显示风格默认的标签页是比较难看的,可以自定义标签显示风格。 TabHost tabHost = (TabHost) this.findViewById(R.id.tabHost); tabHost.setup(); TabSpec tabSpec1 = tabHost.newTabSpec("tab1"); tabSpec1.setIndicator(createTabView("首页")); tabSpec1.setContent(R.id.LinearLayout1); tabHost.addTab(tabSpec1); tabHost.setCurrentTab(0);//设置当前显示标签页为第一页 public View createTabView(String title){ View view = getLayoutInflater().inflate(R.layout.tab_view, null); TextView textView = (TextView) view.findViewById(R.id.tagText); textView.setText(title); return view; } 标签自定义界面请见下面备注:
  • 273. 手势识别<一>第一步:建立手势库 使用SDK自带例子GestureBuilder建立手势库(位置:android-sdk-windows\samples\android-8\GestureBuilder)。使用GestureBuilder之前,你需要恢复其到开发环境,然后进行编绎并部署到手机上。此时,就可以使用GestureBuilder建立手势库,生成的手势库文件在SCDard上,默认文件名称为:gestures 第二步:在应用中加载手势库文件,然后开发手势识别代码。 把手势库文件gestures文件拷贝到项目的res/raw目录下。然后在布局文件中添加用于手势绘制的View:
  • 274. 手势识别<二>为View添加手势监听事件:gestureOverlayView.addOnGesturePerformedListener(); 得到手势库:mLibrary = GestureLibraries.fromRawResource(this, R.raw.gestures); 加载手势库:mLibrary.load(); List predictions = mLibrary.recognize(gesture);//从手势库中查询匹配的内容,匹配的结果可能包括多个相似的内容,匹配度高的结果放在最前面 大多数情况下,手势都是通过一笔完成。然而有一些特别的需求就需要通过多个笔画来实现,这时可以使用gestureStrokeType属性进行设置:android:gestureStrokeType="multiple“手势识别代码见ppt下方
  • 275. android样式和主题(style&theme)<一> android中的样式和CSS样式作用相似,都是用于为界面元素定义显示风格,它是一个包含一个或者多个view控件属性的集合。如:需要定义字体的颜色和大小。 在CSS中是这样定义的: 可以像这样使用上面的css样式:
    王家林
    在Android中可以这样定义样式:
  • 276. android样式和主题(style&theme)<二>在res/values/styles.xml文件中添加以下内容 在layout文件中可以像下面这样使用上面的android样式:
  • 277. android样式和主题(style&theme)<三>
  • 278. android样式和主题(style&theme)<四> android中主题也是用于为应用定义显示风格,它的定义和样式的定义相同,如下: 上面“?android:windowNoTitle”中的问号用于引用在当前主题中定义过的资源的值。下面代码显示在AndroidManifest.xml中如何为应用设置上面定义的主题: ......
  • 279. android样式和主题(style&theme)<五>除了可以在AndroidManifest.xml中设置主题,同样也可以在代码中设置主题,如下: setTheme(R.style.iwangjialinTheme); 尽管在定义上,样式和主题基本相同,但是它们使用的地方不同。样式用在单独的View,如:EditText、TextView等;主题通过AndroidManifest.xml中的用在整个应用或者某个 Activity,主题对整个应用或某个Activity进行全局性影响。如果一个应用使用了主题,同时应用下的view也使用了样式,那么当主题和样式属性发生冲突时,样式的优先级高于主题。 另外android系统也定义了一些主题,例如:,该主题可以让Activity看起来像一个对话框,还有透明主题:@android:style/Theme.Translucent 。如果需要查阅这些主题,可以在文档的referenceandroid-->R.style 中查看。
  • 280. 编码实现软件界面Android除了可以使用xml实现软件界面,还可以通过编码方式实现软件的界面,而且在某种情况下只能采用编码方式实现软件的界面,例如:软件运行时需要根据运算结果决定显示某些内容。如果不是必须,建议使用xml,因为这样可以使应用遵守mvc设计模式,具有良好的软件分层结构。下面代码实现了如HelloWorld项目一样的软件界面: public class MainActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout linearLayout = new LinearLayout(this); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); TextView textView = new TextView(this); textView.setText(R.string.hello); textView.setId(34); LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); linearLayout.addView(textView, textParams); setContentView(linearLayout, layoutParams); } }
  • 281. Android的状态栏通知(Notification)通知用于在状态栏显示消息,消息到来时以图标方式表示。 如果需要查看消息,可以拖动状态栏到屏幕下方即可查看消息。 发送消息的代码如下: //获取通知管理器 NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); int icon = android.R.drawable.stat_notify_chat; long when = System.currentTimeMillis(); //新建一个通知,指定其图标和标题 Notification notification = new Notification(icon, null, when);//第一个参数为图标,第二个参数为短暂提示标题,第三个为通知时间 notification.defaults = Notification.DEFAULT_SOUND;//发出默认声音 notification.flags |= Notification.FLAG_AUTO_CANCEL;//点击通知后自动清除通知 Intent openintent = new Intent(this, OtherActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, openintent, 0);//当点击消息时就会向系统发送openintent意图 notification.setLatestEventInfo(this, “标题”, “我是内容", contentIntent); mNotificationManager.notify(0, notification);//第一个参数为自定义的通知唯一标识
  • 282. 使用网页开发软件界面因为android软件开发分工目前还没有细化,程序员往往需要负责软件界面的开发,虽然软件的界面图片已经由美工设计好了,但如果使用layout技术把软件做成如图片所示的界面确实很困难,而且也比较耗时。Android通过WebView实现了JS代码与Java代码互相通信的功能,使的android软件的界面开发也可以采用HTML网页技术,这样,广大网页美工可以参与进android软件的界面开发工作,从而让程序员从中解脱出来。 具体代码见下方备注
  • 283. 动画(Animation)Android提供了2种动画:具体代码实现见下方备注 1> Tween动画,通过对 View 的内容进行一系列的图形变换 (包括平移、缩放、旋转、改变透明度)来实现动画效果。动画效果的定义可以采用XML来做也可以采用编码来做。Tween动画有4种类型: 2> Frame动画,即顺序播放事先做好的图像,跟放胶片电影类似。开发步骤: (1)把准备好的图片放进项目res/ drawable下。 (2)在项目的res目录下创建文件夹anim,然后在anim文件夹下面定义动画XML文件,文件名称可以自定义。当然也可以采用编码方式定义动画效果(使用AnimationDrawable类)。 (3)为View控件绑定动画效果。调用代表动画的AnimationDrawable的start()方法开始动画。
  • 284. 传感器的使用传感器类型:方向、加速度(重力)、光线、磁场、距离(临近性)、温度等。 具体代码见下方备注 方向传感器: Sensor.TYPE_ORIENTATION 加速度(重力)传感器: Sensor.TYPE_ACCELEROMETER 光线传感器: Sensor.TYPE_LIGHT 磁场传感器: Sensor.TYPE_MAGNETIC_FIELD 距离(临近性)传感器: Sensor.TYPE_PROXIMITY 温度传感器: Sensor.TYPE_TEMPERATURE //获取某种类型的感应器 Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); //注册监听,获取传感器变化值 sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_GAME); 上面第三个参数为采样率:最快、游戏、普通、用户界面。当应用程序请求特定的采样率时,其实只是对传感器子系统的一个建议,不保证特定的采样率可用。 最快: SensorManager.SENSOR_DELAY_FASTEST 最低延迟,一般不是特别敏感的处理不推荐使用,该种模式可能造成手机电力大量消耗,由于传递的为原始数据,算法不处理好将会影响游戏逻辑和UI的性能。 游戏: SensorManager.SENSOR_DELAY_GAME 游戏延迟,一般绝大多数的实时性较高的游戏都使用该级别。 普通: SensorManager.SENSOR_DELAY_NORMAL 标准延迟,对于一般的益智类或EASY级别的游戏可以使用,但过低的采样率可能对一些赛车类游戏有跳帧现象。 用户界面: SensorManager.SENSOR_DELAY_UI 一般对于屏幕方向自动旋转使用,相对节省电能和逻辑处理,一般游戏开发中我们不使用。
  • 285. PopupWindow弹出窗口PopupWindow是一个可以显示在当前Activity之上的浮动容器,PopupWindow弹出的位置是能够改变的,按照有无偏移量,可以分为无偏移和有偏移两种;按照参照对象的不同又可以分为两种:相对某个控件(Anchor锚点)的位置和在父容器内部的相对位置。具体见下方备注。 创建PopupWindow: LayoutInflater mLayoutInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE); View contentView = mLayoutInflater.inflate(R.layout.xxx, null); // R.layout.xxx为界面文件 PopupWindow popupWindow = new PopupWindow(contentView, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); pop.setBackgroundDrawable(new BitmapDrawable()); pop.setFocusable(true);//取的焦点,创建出来的popupwindow默认无焦点 显示PopupWindow的方法: showAsDropDown(View anchor) 相对某个控件的位置(正左下方),无偏移 。 showAsDropDown(View anchor, int xoff, int yoff) 相对某个控件的位置,有偏移,xoff X轴的偏移量,yoff Y轴的偏移量。 showAtLocation(View parent, int gravity, int x, int y) 在父容器的什么位置,gravity为相对位置,如:正中央Gravity.CENTER、下方Gravity.BOTTOM、Gravity.RIGHT|Gravity.BOTTOM右下方等,后面两个参数为x/y轴的偏移量。 关闭PopupWindow: dismiss() 关闭PopupWindow。
  • 286. NinePatch图片NinePatch是一种很有用的PNG图片格式,它可以在特定区域随文字大小进行缩放。如下: 从上图可以看到,背景图片的中间区域会随着文字的大小进行缩放。背景图片是一张NinePatch图片。 NinePatch图片可以使用android自带的draw9patch工具来制作,该工具在SDK安装路径的tools目录下。执行该工具,然后点击“File”->“open 9-path”打开一张用于制作NinePatch图片的原来图片。在画布的上方和左方的边上画线指定缩放区域, 勾选“Show patches”可显示画定的区域,绿色 为固定大小区域,红色为缩放区域,文字会摆放在红色 区域。制作完后,点击“File” “save 9-path”保存 图片,draw9patch工具会自动为图片加上*.9.png后缀。 把制作好的图片拷贝进项目的res/drawable目录,然后 编写代码。如下:
  • 287. 自定义窗口标题1>提供自定义标题界面,如: R.layout.title 2>在Activity的onCreate()方法中添加如下代码: requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.main); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title); 3>为Activity设置主题,修改系统默认标题样式: #00000000 透明色
  • 288. 关闭应用当应用不再使用时,通常需要关闭应用,可以使用以下三种方法关闭android应用: 第一种方法:首先获取当前进程的id,然后杀死该进程。 (建议使用) android.os.Process.killProcess(android.os.Process.myPid()) 第二种方法:终止当前正在运行的Java虚拟机,导致程序终止 System.exit(0); 第三种方法:强制关闭与该包有关联的一切执行 ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); manager.restartPackage(getPackageName());
  • 289. 判断SIM卡属于哪个移动运营商见备注栏: 在文件AndroidManifest.xml中添加权限
  • 290. 从SIM卡中获取联系人信息Uri uri = Uri.parse("content://icc/adn"); String[] projection = {"_id", "name", "number"}; Cursor cursor = managedQuery(uri, projection, null, null, "name"); if(cursor!=null){ while(cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex("name")); String phone = cursor.getString(cursor.getColumnIndex("number")); } } 在文件AndroidManifest.xml中添加权限 Android系统内部通过Contentprovider对外共享Sim卡存放的联系人等信息,你可以通过操作Contentprovider来实现Sim卡信息的添删改查操作。 内部实现源代码参见备注栏:
  • 291. 删除呼叫记录在文件AndroidManifest.xml中添加权限 负责存放呼叫记录的内容提供者源码在ContactsProvider项目下: 源码路径: com\android\providers\contacts\CallLogProvider.java 使用到的数据库在: /data/data/com.android.providers.contacts/databases/contacts2.db 表名:calls 呼叫记录有三种类型: 来电:CallLog.Calls.INCOMING_TYPE (常量值:1) 外拔:CallLog.Calls.OUTGOING_TYPE(常量值:2) 未接:CallLog.Calls.MISSED_TYPE(常量值:3) 具体代码参见备注
  • 292. Google Map的开发1 我们创建模拟器的时候必须选择带有google apis 的 target 2 我们创建的应用也必须是android projection 也必须google apis build target 3 获取apikey (流程见下面的注解) 4 我们创建的activity必须继承于MapActivity
  • 293. 在应用中安装其他程序首先需要AndroidManifest.xml中加入安装程序权限: 第二步把安装程序添加进SDCard。如把文件名为” sogouinput_android_1.40_sweb.apk.zip”的sogou拼音输入法安装文件放进SDCard。可以点击下面按钮: 第三步在程序中添加以下代码: Intent intent = new Intent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(android.content.Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "sogouinput_android_1.40_sweb.apk.zip")),"application/vnd.android.package-archive"); startActivity(intent); 删除应用相见备注
  • 294. 节点的使用 .... 可以根据Item的ID更换不同图片,代码如下: LayerDrawable layerDrawable = (LayerDrawable) getResources().getDrawable(R.drawable.layerlist); layerDrawable.setDrawableByLayerId(R.id.userface, getResources().getDrawable(R.drawable.icon)); imageView.setImageDrawable(layerDrawable);
  • 301. Transition类型图形Transition类型图形可以实现多张图片的过渡显示。 /res/drawable/transition.xml 使用下面代码实现图片过渡: TransitionDrawable drawable = (TransitionDrawable) imageView.getDrawable(); drawable.startTransition(500);
  • 302. Clip类型图形Clip类型图形可以实现图片的裁剪。 /res/drawable/clip.xml 由于默认裁剪级别为0(全部裁剪,导致图片看不见)。当级别为10000时,不裁剪图片,图片全部可见。下面代码可以设置裁剪级别 ImageView imageView = (ImageView) this.findViewById(R.id.imageView); ClipDrawable drawable = (ClipDrawable) imageView.getDrawable(); drawable.setLevel(drawable.getLevel() + 1000);
  • 303. 如何反编绎APK文件安装ApkTool工具,该工具可以解码得到资源文件,但不能得到Java源文件。 安装环境:需要安装JRE1.6 1> 到http://code.google.com/p/android-apktool/下载apktool1.3.2.tar.bz2 和apktool-install-windows-2.2_r01-3.tar.bz2 文件。解压两个文件,然后把解压后的文件放在一起,如:c:\apktool 2> 在系统变量PATH中添加进aapt.exe,如:;c:\apktool\aapt.exe 3> 在DOS窗口下进入apktool.jar所在目录。执行DOS命令:apktool d -s c:\soft\xxx.apk c:\soft\source。 命令格式:apktool d [opts] [dir] 中的d代表解码,[opts]代表选项,-s选项代表不解码源文件。 Apktool工具只能反编译成smali的中间代码文件,这里需要借助另外一个开源工具Dex2Jar,该工具可以把dex文件转换成jar文件。这个工具不能直接翻译成java文件,但是可以把dex文件转换成jar文件 下载地址:http://code.google.com/p/dex2jar/。 1> 把APK安装包中的classes.dex解压到某个目录下,如:c:\soft 2> 在DOS窗口下进入dex2jar.bat所在目录,执行DOS命令:dex2jar.bat c:\soft\source\classes.dex c:\soft\source,命令生成classes.dex.dex2jar.jar文件。 安装jd-gui工具,该工具可以把jar文件反编译成Java源文件 下载地址:http://java.decompiler.free.fr/jd-gui/downloads/jd-gui-0.3.3.windows.zip。 运行该软件,直接打开classes.dex.dex2jar.jar文件即可看到java源代码。
  • 304. 如何防止我们的代码被反编译由于apk是Android虚拟机加载的,它有一定的规范,加密apk后Dalvik无法 识别apk了。完全避免是不可能的,总有人能够破解你的代码。但是有几种 方式来提高被反编译取代码的难度。 1 关键代码使用jni调用本地代码,用c或者c++编写,因此相对比较难于反 编译 2 混淆java代码。混淆是不改变代码逻辑的情况下,增加无用代码,或者重 命名,使反编译后的源代码难于看懂。 网上开源的java代码混淆工具较多,一般是用ant的方式来编译的
  • 305. Android图标下载网址http://www.easyicon.cn/iconsearch/android-icons/1/
  • 306. The Beginning!