• 1. Android核心基础(一)讲师:张泽华
  • 2. 什么是3G第三代数字通讯技术(3rd Generation) 3G与2G的主要区别是在传输声音和数据的速度上的提升 1995年问世的第一代模拟制式手机(1G)只能进行语音通话。 1996到1997年出现的第二代GSM、CDMA等数字制式手机(2G)便增加了接收数据的功能 3G是第三代通信网络,目前国内支持国际电联确定三个无线接口标准 中国电信的CDMA2000 (日、韩和北美使用) 中国联通的WCDMA (世界上大部分3G网络都采用的是该标准) 中国移动的TD-SCDMA (中国) GSM-->GPRS-->EDGE-->WCDMA-->HSDPA-->FDD-LTE长期演进 | | | | | | 9k 42k 172k 364k 7.2M 300M
  • 3. 为什么开发手机程序我们可以开发了 若干年前只有摩托罗拉资深工程师才能搞定 掌握了移动互联网就掌握了将来 谁输在了移动互联网上,谁就输了未来,谁抓住了移动互联网的机会,谁就有了未来成功的门票。 工作好找 智联招聘每周有约5000个移动开发职位 互联网调查公司Dice.com统计: 60% IT公司职业HR在寻找Android工程师 平均薪水高, 黑马训练营2012年 Android平均薪水 ¥7500
  • 4. 为什么选择Android开发设备便宜!!!
  • 5. 为什么选择Android开发市场占有率:
  • 6. 为什么选择Android开发开源 开放 JAVA语言
  • 7. 各种Android设备
  • 8. Galaxy Tablet
  • 9. Android微波炉http://www.pocket-lint.com/news/30712/android-powered-microwave-cooking-google
  • 10. Android智能电视
  • 11. http://www.google.com/nexus/ Google/Samsung Galaxy Nexus
  • 12. 简单历史背景1996年 www网开始流行,可以显示文字和图片 但是最好的手机设备只能显示简单的文本 移动互联网入口: WAP (wait and pay) Wireless Markup Language (WML) – 精简版的html语言
  • 13. Android 简单历史2005 Google收购 Android Inc. 开始 Dalvik VM 的研究 2007 开发手机联盟成立 Sdk1.0预览版发布 2008 Google 第一届手机开发者大赛 Google第一个亲儿子T-Mobile G1 发布 SDK 1.0 发布 Android 开放源代码 (Apache License)
  • 14. Android 进化史1.5 Cupcake(纸杯蛋糕) 1.6 Donut(甜甜圈) 2.1 Eclair(闪电泡芙) 2.2 Froyo(冻酸奶) 2.3 Gingerbread(姜饼) 3.0 Honeycomb(蜂巢) 4.0 Ice cream SandWich(冰激凌三明治) 4.1 Jelly Bean (果冻豆 ) 4.2 Jelly Bean (果冻豆 ) 5.0 Lime Pie (酸橙派)
  • 15. Android是什么手机设备的软件栈,包括 一个完整的操作系统 中间件 关键的应用程序 底层是linux内核 安全管理 内存管理 进程管理 电源管理 硬件驱动
  • 16. Android 体系结构
  • 17. Dalvik VM 和 JVM 的比较区别 jvm dalvik vm 编译后文件格式 .java->.class->.jar .java->.class->.dex->.odex 基于的架构 基于栈的架构 基于寄存器的架构 jdk javacsdk dex
  • 18. Dalvik VM 和 JVM的比较
  • 19. Android开发环境搭建获取SDK 工具包 下载地址:http://dl.google.com/android/adt/adt-bundle-windows-x86.zip 工具包,包含以下内容: Eclipse + ADT 插件 Android SDK Android Platform-tools 最新的Android 开发平台 最新的模拟器镜像
  • 20. 如何安装 Android SDK 和Eclipse 插件
  • 21. 开发工具简介Eclipse SDK Manager SDK
  • 22. 创建一个Android模拟器点击手机形状的图形(android virtual device manager) 创建一个新的android模拟器
  • 23. Android模拟器无法启动错误提示为: invalid command-line parameter: Files\Android\android-sdk\tools/emulator-arm.exe.Hint: use '@foo' to launch a virtual device named 'foo'.please use -help for more information 解决方法: 1. 你的安装路径中有中文,那么就重新安装SDK,放在全英文路径下 2. 添加Android_SDK_HOME 环境变量,从变量名上看最好把ANDROID_SDK_HOME指向Android SDK目录,然后启动AVD
  • 24. Android模拟器无法保存数据原因: 电脑意外重启,或者模拟器非法关闭. 正常情况下,当模拟器被开启后. 在x:\Documents and Settings\Administrator\.android\avd\xxx.avd的目录下会产生 cache.img.lock, hardware-qemu.ini.lock, sdcard.img.lock, userdata-qemu.img.lock这样的文件夹,用来标记当前模拟器设备已经开启,当模拟器设备关闭的时候 会自动删除这些文件夹. 如果电脑意外重启或者模拟器被非法关闭. 这几个文件夹是不会被自动删除的. 系统任务android模拟器还处于打开状态. 新开启的模拟器就无法保存数据了. 解决方法: 删除这些.lock的文件夹
  • 25. “尚未注册网络”错误信息的解决办法打开Android模拟器时,出现无信号,拔打电话或发短信时,提示“尚未注册网络”错误信息的解决方案如下。 场景一:你的电脑没有连接上互联网,同时也没有在局域网。 解决办法:右键点击网上邻居,选择"属性",在网络连接窗口中右键点击"本地连接",选择"属性",设置TCP/IP属性如下: IP地址:192.168.1.100 子网掩码:255.255.255.0 默认网关:192.168.1.100 首选DNS服务器:192.168.1.100 场景二:你的电脑没有连接上互联网,但在局域网。 解决办法:右键点击网上邻居,选择"属性",在网络连接窗口中右键点击"本地连接",选择"属性",设置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 最后一种解决方案是:让你的电脑连接上互联网。
  • 26. 常见命令操作adb devices 列出所有的设备 adb shell 挂载到linux的空间 adb install xxx.apk 如果有多个设备,我们可以指定设备 adb install –s emulator-5554 D:/xxx.apk emulator –avd advname 启动模拟器 例如:emulator –avd 2.2 (2.2 为我们创建设备的名称) mkmdcard 20m d:/sdcard.img 创建sdcard adb pull adb push android create avd –name android2.2 –target 8 创建模拟器 ctrl + F11 横竖屏的切换
  • 27. 开发第一个Android应用打开eclipse  File  New  Android Application project
  • 28. Android平台占有率为了保证程序能够在大多数手机上运行,需要保证至少兼容到2.2
  • 29. Android应用程序架构src/ java原代码存放目录 gen/ 自动生成目录 gen 目录中存放所有由Android开发工具自动生成的文件。目录中最重要的就是R.java文件。 这个文件由Android开发工具自动产生的。Android开发工具会自动根据你放入res目录的资源,同步更新修改R.java文件。正因为R.java文件是由开发工具自动生成的,所以我们应避免手工修改R.java。R.java在应用中起到了字典的作用,它包含了各种资源的id,通过R.java,应用可以很方便地找到对应资源。另外编绎器也会检查R.java列表中的资源是否被使用到,没有被使用到的资源不会编绎进软件中,这样可以减少应用在手机占用的空间。 res/ 资源(Resource)目录 在这个目录中我们可以存放应用使用到的各种资源,如xml界面文件,图片或数据。具体请看ppt下方备注栏。 libs/ 支持库目录 程序开发时需要的一些三方的jar包可以放在这个目录,系统会自动把里面的jar包,添加到环境变量。 assets资源目录 Android除了提供/res目录存放资源文件外,在/assets目录也可以存放资源文件,而且/assets目录下的资源文件不会在R.java自动生成ID,所以读取/assets目录下的文件必须指定文件的路径,如:file:///android_asset/xxx.3gp AndroidManifest.xml 项目清单文件 这个文件列出了应用程序所提供的功能,以后你开发好的各种组件需要在该文件中进行配置,如果应用使用到了系统内置的应用(如电话服务、互联网服务、短信服务、GPS服务等等),你还需在该文件中声明使用权限。 project.properties 项目环境信息,一般是不需要修改此文件
  • 30. 程序打包&安装的过程 ADB (android debug bridge) 为开发人员提供便利 IDE Eclipse 把上面的过程全部都自动实现了 Compiled resources (xml files)Android Debug Bridge
  • 31. Android 应用程序使用 Java open sdk的变种 不支持部分 Java 库 : Swing & AWT Oracle 正在跟Google打官司 Java code 编译成 Dalvik byte code (.dex) 专门为手机设备优化 (更好的内存管理, 电源优化, 等.) Dalvik VM 运行 .dex 文件
  • 32. 黑马训练营 www.itheima.com 电话拔号器效果图:
  • 33. 黑马训练营 www.itheima.com 电话拔号器因为应用要使用手机的电话服务,所以要在清单文件AndroidManifest.xml中添加电话服务权限: 略....
  • 34. 黑马训练营 www.itheima.com 电话拔号器界面布局:
  • 35. 黑马训练营 www.itheima.comAndroid中的显示单位 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。
  • 36. 黑马训练营 www.itheima.com 电话拔号器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); } }); } }
  • 37. 黑马训练营 www.itheima.com 电话拔号器测试步骤: 1>在Eclipse中运行此应用 2>在Dos窗口中进入android SDK安装路径的tools目录,输入以下命令再开启一个Android模拟器: emulator -data itcast 注:itcast为用户数据存取文件,如果该文件不存在,默认在tools目录创建该文件 3>在电话扰号器中输入上图现显的电话号码
  • 38. 黑马训练营 www.itheima.com 短信发送器效果图:
  • 39. 黑马训练营 www.itheima.com 短信发送器因为应用要使用手机的短信服务,所以要在清单文件AndroidManifest.xml中添加短信服务权限: 略....
  • 40. 黑马训练营 www.itheima.com 短信发送器界面布局:
  • 41. 黑马训练营 www.itheima.com 短信发送器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();
  • 42. 黑马训练营 www.itheima.com 短信发送器测试步骤: 1>在Eclipse中运行此应用 2>在Dos窗口中进入android SDK安装路径的tools目录,输入以下命令再开启一个Android模拟器: emulator -data itcast 注:itcast为用户数据存取文件,如果该文件不存在,默认在tools目录创建该文件 3>在短信发送器的手机号中输入上图现显的电话号码 注:目前Android系统对中文短信尚未支持,所以发送中文短信会有乱码,这个问题日后会被解决的。
  • 43. 黑马训练营 www.itheima.com对应用进行单元测试在实际开发中,开发android软件的过程需要不断地进行测试。而使用Junit测试框架,侧是正规Android开发的必用技术,在Junit中可以得到组件,可以模拟发送事件和检测程序处理的正确性。 第一步:首先在AndroidManifest.xml中加入下面红色代码: .... 上面targetPackage指定的包要和应用的package相同。 第二步:编写单元测试代码(选择要测试的方法,右键点击“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); } }
  • 44. 黑马训练营 www.itheima.com 数据存储与访问很多时候我们的软件需要对处理后的数据进行存储或再次访问。Android为数据存储提供了如下几种方式: 文件 SharedPreferences(参数) SQLite数据库 内容提供者(Content provider) 网络
  • 45. 黑马训练营 www.itheima.com 使用文件进行数据存储首先给大家介绍使用文件如何对数据进行存储,Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的实现过程与在J2SE环境中保存数据到文件中是一样的。 public class FileActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { ... FileOutputStream outStream = this.openFileOutput("itcast.txt", Context.MODE_PRIVATE); outStream.write("传智播客".getBytes()); outStream.close(); } } openFileOutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它。创建的文件保存在/data/data//files目录,如: /data/data/cn.itcast.action/files/itcast.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
  • 46. 黑马训练营 www.itheima.com 使用文件进行数据存储Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可以使用Context.MODE_APPEND Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件。 MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。 如果希望文件被其他应用读和写,可以传入: openFileOutput("itcast.txt", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); android有一套自己的安全模型,当应用程序(.apk)在安装时系统就会分配给他一个userid,当该应用要去访问其他资源比如文件的时候,就需要userid匹配。默认情况下,任何应用创建的文件,sharedpreferences,数据库都应该是私有的(位于/data/data//files),其他程序无法访问。除非在创建时指定了Context.MODE_WORLD_READABLE或者Context.MODE_WORLD_WRITEABLE ,只有这样其他程序才能正确访问。
  • 47. 黑马训练营 www.itheima.com 读取文件内容如果要打开存放在/data/data//files目录应用私有的文件,可以使用Activity提供openFileInput()方法。 FileInputStream inStream = this.getContext().openFileInput("itcast.txt"); Log.i("FileTest", readInStream(inStream)); readInStream()的方法请看本页下面备注。 或者直接使用文件的绝对路径: File file = new File("/data/data/cn.itcast.action/files/itcast.txt"); FileInputStream inStream = new FileInputStream(file); Log.i("FileTest", readInStream(inStream)); 注意:上面文件路径中的“cn.itcast.action”为应用所在包,当你在编写代码时应替换为你自己应用使用的包。 对于私有文件只能被创建该文件的应用访问,如果希望文件能被其他应用读和写,可以在创建文件时,指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限。 Activity还提供了getCacheDir()和getFilesDir()方法: getCacheDir()方法用于获取/data/data//cache目录 getFilesDir()方法用于获取/data/data//files目录
  • 48. 黑马训练营 www.itheima.com 把文件存放在SDCard使用Activity的openFileOutput()方法保存文件,文件是存放在手机空间上,一般手机的存储空间不是很大,存放些小文件还行,如果要存放像视频这样的大文件,是不可行的。对于像视频这样的大文件,我们可以把它存放在SDCard。 SDCard是干什么的?你可以把它看作是移动硬盘或U盘。 在模拟器中使用SDCard,你需要先创建一张SDCard卡(当然不是真的SDCard,只是镜像文件)。创建SDCard可以在Eclipse创建模拟器时随同创建,也可以使用DOS命令进行创建,如下: 在Dos窗口中进入android SDK安装路径的tools目录,输入以下命令创建一张容量为2G的SDCard,文件后缀可以随便取,建议使用.img: mksdcard 2048M D:\AndroidTool\sdcard.img 在程序中访问SDCard,你需要申请访问SDCard的权限。 在AndroidManifest.xml中加入访问SDCard的权限如下:
  • 49. 黑马训练营 www.itheima.com 把文件存放在SDCard要往SDCard存放文件,程序必须先判断手机是否装有SDCard,并且可以进行读写。 注意:访问SDCard必须在AndroidManifest.xml中加入访问SDCard的权限 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File sdCardDir = Environment.getExternalStorageDirectory();//获取SDCard目录 File saveFile = new File(sdCardDir, “itcast.txt”); FileOutputStream outStream = new FileOutputStream(saveFile); outStream.write("传智播客".getBytes()); outStream.close(); } Environment.getExternalStorageState()方法用于获取SDCard的状态,如果手机装有SDCard,并且可以进行读写,那么方法返回的状态等于Environment.MEDIA_MOUNTED。 Environment.getExternalStorageDirectory()方法用于获取SDCard的目录,当然要获取SDCard的目录,你也可以这样写: File sdCardDir = new File("/mnt/sdcard"); //获取SDCard目录 File saveFile = new File(sdCardDir, "itcast.txt"); //上面两句代码可以合成一句: File saveFile = new File("/mnt/sdcard/itcast.txt"); FileOutputStream outStream = new FileOutputStream(saveFile); outStream.write("传智播客test".getBytes()); outStream.close();
  • 50. 黑马训练营 www.itheima.com 使用pull解析XML文件 下面是本例子要解析的XML文件: 文件名称:itcast.xml allen 36 james 25 例子定义了一个javabean用于存放上面解析出来的xml内容, 这个javabean为Person,代码请见本页下面备注:
  • 51. 黑马训练营 www.itheima.com 使用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解析器读取itcast.xml的代码在本页下方备注 Pull解析器的源码及文档下载网址:http://www.xmlpull.org/ Xml.newPullParser()--->setInput-->getEventType()--->while(type!=XmlPullParser.END_DOCUMENT)--> case type --> parser.getName()去判断---->获得的信息添加进对象---> type = parser.next();指针下移
  • 52. 黑马训练营 www.itheima.com 使用Pull解析器生成XML文件有些时候,我们需要生成一个XML文件,生成XML文件的方法有很多,如:可以只使用一个StringBuilder组拼XML内容,然后把内容写入到文件中;或者使用DOM API生成XML文件,或者也可以使用pull解析器生成XML文件,这里推荐大家使用Pull解析器。XmlSerializer 使用Pull解析器生成一个与itcast.xml文件内容相同的myitcast.xml文件,代码在本页下方备注 使用代码如下(生成XML文件): File xmlFile = new File("myitcast.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(); XmlSerializer
  • 53. 黑马训练营 www.itheima.com 使用SharedPreferences进行数据存储很多时候我们开发的软件需要向用户提供软件参数设置功能,例如我们常用的QQ,用户可以设置是否允许陌生人添加自己为好友。对于软件配置参数的保存,如果是window软件通常我们会采用ini文件进行保存,如果是j2se应用,我们会采用properties属性文件或者xml进行保存。如果是Android应用,我们最适合采用什么方式保存软件配置参数呢?Android平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data//shared_prefs目录下: SharedPreferences sharedPreferences = getSharedPreferences("itcast", Context.MODE_PRIVATE); Editor editor = sharedPreferences.edit();//获取编辑器 editor.putString("name", "传智播客"); editor.putInt("age", 4); editor.commit();//提交修改 生成的itcast.xml文件内容如下: 传智播客 因为SharedPreferences背后是使用xml文件保存数据,getSharedPreferences(name,mode)方法的第一个参数用于指定该文件的名称,名称不用带后缀,后缀会由Android自动加上。方法的第二个参数指定文件的操作模式,共有四种操作模式,这四种模式前面介绍使用文件方式保存数据时已经讲解过。如果希望SharedPreferences背后使用的xml文件能被其他应用读和写,可以指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限。 另外Activity还提供了另一个getPreferences(mode)方法操作SharedPreferences,这个方法默认使用当前类不带包名的类名作为文件的名称。
  • 54. 黑马训练营 www.itheima.com 访问SharedPreferences中的数据访问SharedPreferences中的数据代码如下: SharedPreferences sharedPreferences = getSharedPreferences("itcast", 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权限。如:有个为cn.itcast.action的应用使用下面语句创建了preference。 getSharedPreferences("itcast", Context.MODE_WORLD_READABLE); 其他应用要访问上面应用的preference,首先需要创建上面应用的Context,然后通过Context 访问preference ,访问preference时会在应用所在包下的shared_prefs目录找到preference : Context otherAppsContext = createPackageContext("cn.itcast.action", Context.CONTEXT_IGNORE_SECURITY); SharedPreferences sharedPreferences = otherAppsContext.getSharedPreferences("itcast", 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/itcast.xml”);//应替换成应用的包名
  • 55. 黑马训练营 www.itheima.com 使用嵌入式关系型SQLite数据库存储数据在Android平台上,集成了一个嵌入式关系型数据库—SQLite,SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型,虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等数据类型,只不过在运算或保存时会转成对应的五种数据类型。 SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用关心字段声明的数据类型是什么。例如:可以在Integer类型的字段中存放字符串,或者在布尔型字段中存放浮点数,或者在字符型字段中存放日期型值。 但有一种情况例外:定义为INTEGER PRIMARY KEY的字段只能存储64位整数, 当向这种字段保存除整数以外的数据时,将会产生错误。 另外, SQLite 在解析CREATE TABLE 语句时,会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息,如下面语句会忽略 name字段的类型信息: CREATE TABLE person (personid integer primary key autoincrement, name varchar(20)) 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(‘传智’,3) 更新语句:update 表名 set 字段名=值 where 条件子句。如:update person set name=‘传智‘ where id=10 删除语句:delete from 表名 where 条件子句。如:delete from person where id=10
  • 56. 黑马训练营 www.itheima.com 使用SQLiteOpenHelper对数据库进行版本管理我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用软件时创建出应用使用到的数据库表结构及添加一些初始化记录,另外在软件升级的时候,也需要对数据表结构进行更新。那么,我们如何才能实现在用户初次使用或升级软件时自动在用户的手机上创建出应用需要的数据库表呢?总不能让我们在每个需要安装此软件的手机上通过手工方式创建数据库表吧?因为这种需求是每个数据库应用都要面临的,所以在Android系统,为我们提供了一个名为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()方法先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
  • 57. 黑马训练营 www.itheima.com 使用SQLiteOpenHelper对数据库进行版本管理public class DatabaseHelper extends SQLiteOpenHelper { //类没有实例化,是不能用作父类构造器的参数,必须声明为静态 private static final String name = "itcast"; //数据库名称 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 删除表 } } 在实际项目开发中,当数据库表结构发生更新时,应该避免用户存放于数据库中的数据丢失。
  • 58. 黑马训练营 www.itheima.com 使用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('传智播客', 4)"); db.close(); 执行上面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[]{"传智播客", 4}); db.close(); execSQL(String sql, Object[] bindArgs)方法的第一个参数为SQL语句,第二个参数为SQL语句中占位符参数的值,参数值在数组中的顺序要和占位符的位置对应。
  • 59. 黑马训练营 www.itheima.com 使用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(); rawrawQuery()方法的第一个参数为select语句;第二个参数为select语句中占位符参数的值,如果select语句没有使用占位符,该参数可以设置为null。带占位符参数的select语句使用例子如下: Cursor cursor = db.rawQuery("select * from person where name like ? and age=?", new String[]{"%传智%", "4"}); Cursor是结果集游标,用于对结果集进行随机访问,如果大家熟悉jdbc, 其实Cursor与JDBC中的ResultSet作用很相似。使用moveToNext()方法可以将游标从当前行移动到下一行,如果已经移过了结果集的最后一行,返回结果为false,否则为true。另外Cursor 还有常用的moveToPrevious()方法(用于将游标从当前行移动到上一行,如果已经移过了结果集的第一行,返回值为false,否则为true )、moveToFirst()方法(用于将游标移动到结果集的第一行,如果结果集为空,返回值为false,否则为true )和moveToLast()方法(用于将游标移动到结果集的最后一行,如果结果集为空,返回值为false,否则为true ) 。
  • 60. 黑马训练营 www.itheima.com 使用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", 4); long rowid = db.insert(“person”, null, values);//返回新添记录的行号,与主键id无关 不管第三个参数是否包含数据,执行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。
  • 61. 黑马训练营 www.itheima.com 使用SQLiteDatabase操作SQLite数据库delete()方法的使用: SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.delete("person", "personid
  • 62. 黑马训练营 www.itheima.com 使用SQLiteDatabase操作SQLite数据库query()方法实际上是把select语句拆分成了若干个组成部分,然后作为方法的输入参数: SQLiteDatabase db = databaseHelper.getWritableDatabase(); Cursor cursor = db.query("person", new String[]{"personid,name,age"}, "name like ?", new String[]{"%传智%"}, 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字段含有“传智”的记录,匹配的记录按personid降序排序,对排序后的结果略过第一条记录,只获取2条记录。 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关键字后面的部分。
  • 63. 黑马训练营 www.itheima.com 使用SQLiteOpenHelper获取用于操作数据库的SQLiteDatabase实例public class DatabaseHelper extends SQLiteOpenHelper { private static final String name = "itcast"; //数据库名称 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[]{"传智播客", 4}); db.close(); }}); } } 第一次调用getWritableDatabase()或getReadableDatabase()方法后,SQLiteOpenHelper会缓存当前的SQLiteDatabase实例,SQLiteDatabase实例正常情况下会维持数据库的打开状态,所以在你不再需要SQLiteDatabase实例时,请及时调用close()方法释放资源。一旦SQLiteDatabase实例被缓存,多次调用getWritableDatabase()或getReadableDatabase()方法得到的都是同一实例。
  • 64. 黑马训练营 www.itheima.com 使用事务操作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语句在同一个事务中执行。
  • 65. 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
  • 66. 黑马训练营 www.itheima.com 使用ContentProvider(内容提供者)共享数据ContentProvider 在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查。关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。那么,这里为何要使用ContentProvider 对外共享数据呢?是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。 使用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)} 第二步需要在AndroidManifest.xml使用对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider , ContentProvider 采用了authorities(主机名/域名)对它进行唯一标识,你可以把 ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:
  • 67. 黑马训练营 www.itheima.com Uri介绍Uri代表了要操作的数据,Uri主要包含了两部分信息:1》需要操作的ContentProvider ,2》对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content:// 主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。 路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下: 要操作person表中id为10的记录,可以构建这样的路径:/person/10 要操作person表中id为10的记录的name字段, person/10/name 要操作person表中的所有记录,可以构建这样的路径:/person 要操作xxx表中的记录,可以构建这样的路径:/xxx 当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下: 要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name 如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下: Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person")
  • 68. 黑马训练营 www.itheima.com UriMatcher类使用介绍因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。 UriMatcher类用于匹配Uri,它的用法如下: 首先第一步把你需要匹配Uri路径全部给注册上,如下: //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码 UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); //如果match()方法匹配content://cn.itcast.provider.personprovider/person路径,返回匹配码为1 sMatcher.addURI(“cn.itcast.provider.personprovider”, “person”, 1);//添加需要匹配uri,如果匹配就会返回匹配码 //如果match()方法匹配content://cn.itcast.provider.personprovider/person/230路径,返回匹配码为2 sMatcher.addURI(“cn.itcast.provider.personprovider”, “person/#”, 2);//#号为通配符 switch (sMatcher.match(Uri.parse("content://cn.itcast.provider.personprovider/person/10"))) { case 1 break; case 2 break; default://不匹配 break; } 注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://cn.itcast.provider.personprovider/person路径,返回的匹配码为1
  • 69. 黑马训练营 www.itheima.com ContentUris类使用介绍ContentUris类用于获取Uri路径后面的ID部分,它有两个比较实用的方法: withAppendedId(uri, id)用于为路径加上ID部分: Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person") Uri resultUri = ContentUris.withAppendedId(uri, 10); //生成后的Uri为:content://cn.itcast.provider.personprovider/person/10 parseId(uri)方法用于从路径中获取ID部分: Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person/10") long personid = ContentUris.parseId(uri);//获取的结果为:10
  • 70. 黑马训练营 www.itheima.com 使用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中的数据。 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://cn.itcast.provider.personprovider/person,那么返回的MIME类型字符串应该为:“vnd.android.cursor.dir/person”。如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,例如:得到id为10的person记录,Uri为content://cn.itcast.provider.personprovider/person/10,那么返回的MIME类型字符串应该为:“vnd.android.cursor.item/person”。
  • 71. 黑马训练营 www.itheima.com 使用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中的数据。 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于从ContentProvider中获取数据。 这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作,假设给定的是: Uri.parse(“content://cn.itcast.providers.personprovider/person/10”),那么将会对主机名为cn.itcast.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。
  • 72. 黑马训练营 www.itheima.com 使用ContentResolver操作ContentProvider中的数据使用ContentResolver对ContentProvider中的数据进行添加、删除、修改和查询操作: ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person"); //添加一条记录 ContentValues values = new ContentValues(); values.put("name", "itcast"); values.put("age", 25); resolver.insert(uri, values); //获取person表中所有记录 Cursor cursor = resolver.query(uri, null, null, null, "personid desc"); while(cursor.moveToNext()){ Log.i("ContentTest", "personid="+ cursor.getInt(0)+ ",name="+ cursor.getString(1)); } //把id为1的记录的name字段值更改新为liming ContentValues updateValues = new ContentValues(); updateValues.put("name", "liming"); 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);
  • 73. 黑马训练营 www.itheima.com 监听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); } } 如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法: getContentResolver().registerContentObserver(Uri.parse("content://cn.itcast.providers.personprovider/person"), true, new PersonObserver(new Handler())); public class PersonObserver extends ContentObserver{ public PersonObserver(Handler handler) { super(handler); } public void onChange(boolean selfChange) { //此处可以进行相应的业务处理 } }
  • 74. 黑马训练营 www.itheima.com 窃听用户发出的短信用户使用系统自带的短信程序发送短信,程序会通过ContentProvider把短信保存进数据库,并且发出一个数据变化通知,使用ContentObserver对数据变化进行监听,在用户发送短信时,就会被ContentObserver窃听到短信: 注册监听: getContentResolver().registerContentObserver(Uri.parse("content://sms"), true, new SmsObserver(new Handler())); 监听类: 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()); } } }
  • 75. 黑马训练营 www.itheima.com 通信录操作使用ContentResolver对通信录中的数据进行添加、删除、修改和查询操作: 加入读写联系人信息的权限 添加与查询代码请见ppt下方
  • 76. 黑马训练营 www.itheima.com从Internet获取数据利用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(); //第一个参数为输入流,第二个参数为字符集编码 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); }
  • 77. 黑马训练营 www.itheima.com从Internet获取数据利用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"); 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(); }
  • 78. 黑马训练营 www.itheima.com向Internet发送请求参数利用HttpURLConnection对象,我们可以向网络发送请求参数. String requestUrl = "http://localhost:8080/itcast/contanctmanage.do"; Map requestParams = new HashMap(); requestParams.put("age", "12"); requestParams.put("name", "中国"); 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("&"); } 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("Connection", "Keep-Alive");//维持长连接 conn.setRequestProperty("Charset", "UTF-8"); 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); }
  • 79. 黑马训练营 www.itheima.com向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/itcast/contanctmanage.do?method=readxml"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5* 1000); conn.setDoOutput(true);//允许输出 conn.setUseCaches(false);//不使用Cache conn.setRequestMethod("POST"); 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();
  • 80. 黑马训练营 www.itheima.com多线程断点续传下载使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多。如:假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由CPU划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用了99个用户的资源,假设一秒内CPU分配给每条线程的平均执行时间是10ms,A应用在服务器中一秒内就得到了990ms的执行时间,而其他应用在一秒内只有10ms的执行时间。就如同一个水龙头,每秒出水量相等的情况下,放990毫秒的水 肯定比放10毫秒的水要多。 多线程下载的实现过程: 1>首先得到下载文件的长度,然后设置本地文件 的长度。 HttpURLConnection.getContentLength(); RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","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("QQWubiSetup.exe ","rwd"); threadfile.seek(2097152);//从文件的什么位置开始写入数据
  • 81. 黑马训练营 www.itheima.com 为应用添加新的Activity第一步:新建一个继承Activity的类,如:NewActivity public class NewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //这里可以使用setContentView(R.layout.xxx)显示某个视图.... } } 第二步:需要在功能清单AndroidManifest.xml文件中添加进上面Activity配置代码(红色部分): ..... ... android:name属性值的前面加了一个点表示NewActivity是当前包cn.itcast.action下的类,如果类在应用的当前包下,可以省略点符号,如果类在应用的子包下必须加点,如:NewActivity类在cn.itcast.action.user包下可以这样写:
  • 82. 黑马训练营 www.itheima.comActivity生命周期Activity有三个状态: 当它在屏幕前台时(位于当前任务堆栈的顶部),它是激活或运行状态。它就是响应用户操作的Activity。 当它上面有另外一个Activity,使它失去了焦点但仍然对用户可见时(如右图),它处于暂停状态。在它之上的Activity没有完全覆盖屏幕,或者是透明的,被暂停的Activity仍然对用户可见,并且是存活状态(它保留着所有的状态和成员信息并保持和窗口管理器的连接)。如果系统处于内存不足时会杀死这个Activity。 当它完全被另一个Activity覆盖时则处于停止状态。它仍然保留所有的状态和成员信息。然而对用户是不可见的,所以它的窗口将被隐藏,如果其它地方需要内存,则系统经常会杀死这个Activity。 当Activity从一种状态转变到另一种状态时,会调用以下保护方法来通知这种变化: void onCreate(Bundle savedInstanceState) void onStart() void onRestart() void onResume() void onPause() void onStop() void onDestroy()
  • 83. 黑马训练营 www.itheima.comActivity生命周期这七个方法定义了Activity的完整生命周期。实现这些方法可以帮助我们监视其中的三个嵌套生命周期循环: Activity的完整生命周期自第一次调用onCreate()开始,直至调用onDestroy()为止。Activity在onCreate()中设置所有“全局”状态以完成初始化,而在onDestroy()中释放所有系统资源。例如,如果Activity有一个线程在后台运行从网络下载数据,它会在+onCreate()创建线程,而在 onDestroy()销毁线程。 Activity的可视生命周期自onStart()调用开始直到相应的onStop()调用结束。在此期间,用户可以在屏幕上看到Activity,尽管它也许并不是位于前台或者也不与用户进行交互。在这两个方法之间,我们可以保留用来向用户显示这个Activity所需的资源。例如,当用户不再看见我们显示的内容时,我们可以在onStart()中注册一个BroadcastReceiver来监控会影响UI的变化,而在onStop()中来注消。onStart() 和 onStop() 方法可以随着应用程序是否为用户可见而被多次调用。 Activity的前台生命周期自onResume()调用起,至相应的onPause()调用为止。在此期间,Activity位于前台最上面并与用户进行交互。Activity会经常在暂停和恢复之间进行状态转换——例如当设备转入休眠状态或者有新的Activity启动时,将调用onPause() 方法。当Activity获得结果或者接收到新的Intent时会调用onResume() 方法。关于前台生命周期循环的例子请见PPT下方备注栏。
  • 84. 黑马训练营 www.itheima.comActivity生命周期
  • 85. 黑马训练营 www.itheima.comActivity的onSaveInstanceState()和 onRestoreInstanceState()方法Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。 另外,当屏幕的方向发生了改变, 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); } }
  • 86. 横竖屏幕切换默认情况下,当“屏幕方向”或“键盘显示隐藏” 变化时都会销毁当前Activity,创建新的Activity。如果不希望重新创建Activity实例,可以按如下配置Activity: 上面的android:configChanges属性指定了要捕获“屏幕方向”和“键盘显示隐藏”变化,当捕获到这些变化后会调用Activity的onConfigurationChanged()方法。 默认情况下(没有配置android:configChanges属性): 竖屏切横屏,销毁当前Activity之后,创建一个新Activity实例。 横屏切竖屏,销毁当前Activity之后,创建一个新Activity实例,新的Activity实例很快就被销毁,接着又会创建一个新Activity实例。如果只希望创建一个实例,可以配置android:configChanges="orientation"
  • 87. 黑马训练营 www.itheima.com 打开新的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)); }}); } }
  • 88. 黑马训练营 www.itheima.com 打开新的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); }}); } } 在新的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"); } }
  • 89. 黑马训练营 www.itheima.com 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(); ...... } 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()方法会把转换后的值返回。
  • 90. 黑马训练营 www.itheima.com 为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", "传智播客"); 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; }
  • 91. 黑马训练营 www.itheima.com 得到新打开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); }}); } //第一个参数为请求码,即调用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返回的数据。
  • 92. 黑马训练营 www.itheima.com 得到新打开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 }}); } } 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; }
  • 93. 黑马训练营 www.itheima.com 请求码的作用使用startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,我们需要为startActivityForResult()方法传入一个请求码(第二个参数)。请求码的值是根据业务需要由自已设定,用于标识请求来源。例如:一个Activity有两个按钮,点击这两个按钮都会打开同一个Activity,不管是那个按钮打开新Activity,当这个新Activity关闭后,系统都会调用前面Activity的onActivityResult(int requestCode, int resultCode, Intent data)方法。在onActivityResult()方法如果需要知道新Activity是由那个按钮打开的,并且要做出相应的业务处理,这时可以这样做: @Override public void onCreate(Bundle savedInstanceState) { .... button1.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { startActivityForResult (new Intent(MainActivity.this, NewActivity.class), 1); }}); button2.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { startActivityForResult (new Intent(MainActivity.this, NewActivity.class), 2); }}); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch(requestCode){ case 1: //来自按钮1的请求,作相应业务处理 case 2: //来自按钮2的请求,作相应业务处理 } } }
  • 94. 黑马训练营 www.itheima.com 结果码的作用在一个Activity中,可能会使用startActivityForResult()方法打开多个不同的Activity处理不同的业务,当这些新Activity关闭后,系统都会调用前面Activity的onActivityResult(int requestCode, int resultCode, Intent data)方法。为了知道返回的数据来自于哪个新Activity,在onActivityResult()方法中可以这样做(ResultActivity和NewActivity为要打开的新Activity): public class ResultActivity extends Activity { ..... ResultActivity.this.setResult(1, intent); ResultActivity.this.finish(); } public class NewActivity extends Activity { ...... NewActivity.this.setResult(2, intent); NewActivity.this.finish(); } public class MainActivity extends Activity { // 在该Activity会打开ResultActivity和NewActivity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch(resultCode){ case 1: // ResultActivity的返回数据 case 2: // NewActivity的返回数据 } } }
  • 95. 黑马训练营 www.itheima.com Intent(意图)Android基本的设计理念是鼓励减少组件间的耦合,因此Android提供了Intent (意图) ,Intent提供了一种通用的消息系统,它允许在你的应用程序与其它的应用程序间传递Intent来执行动作和产生事件。使用Intent可以激活Android应用的三个核心组件:活动、服务和广播接收器。 Intent可以划分成显式意图和隐式意图。 显式意图:调用Intent.setComponent()或Intent.setClass()方法明确指定了组件名的Intent为显式意图,显式意图明确指定了Intent应该传递给哪个组件。 隐式意图:没有明确指定组件名的Intent为隐式意图。 Android系统会根据隐式意图中设置的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件来处理这个意图。查找规则请见ppt下方备注。
  • 96. 黑马训练营 www.itheima.com应用的响应性(Responsive)在Android中,应用的响应性被活动管理器(Activity Manager) 和窗口管理器(Window Manager)这两个系统服务所监视。 当用户触发了输入事件(如键盘输入,点击按钮等), 如果应用6秒内没有响应用户的输入事件,那么,Android会认 为该应用无响应,便弹出ANR(Application No Response) 对话框。如右图。 在正常情况下,Android程序会在一条单线程里运行。如果Activity要处理一件比较耗时的工作,应该交给子线程完成,否侧会因为主线程被阻塞,后面的用户输入事件因没能在5秒内响应,导致应用出现ANR对话框。
  • 97. 黑马训练营 www.itheima.com广播接收者--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) { } } 第二步:订阅感兴趣的广播Intent,订阅方法有两种: 第一种:使用代码进行订阅 IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED"); IncomingSMSReceiver receiver = new IncomingSMSReceiver(); registerReceiver(receiver, filter); 第二种:在AndroidManifest.xml文件中的节点里进行订阅:
  • 98. 黑马训练营 www.itheima.com使用广播接收者窃听短信如果你想窃听别人接收到的短信,达到你不可告人的目的,那么本节内容可以实现你的需求。 当系统收到短信时,会发出一个广播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); }}}}} 在AndroidManifest.xml文件中的节点里对接收到短信的广播Intent进行订阅: 在AndroidManifest.xml文件中添加以下权限:
  • 99. 黑马训练营 www.itheima.com广播接收者的响应在Android中,每次广播消息到来时都会创建BroadcastReceiver实例并执行onReceive() 方法, onReceive() 方法执行完后,BroadcastReceiver 的实例就会被销毁。当onReceive() 方法在10秒内没有执行完毕,Android会认为该程序无响应。所以在BroadcastReceiver里不能做一些比较耗时的操作,否侧会弹出ANR(Application No Response)的对话框。如果需要完成一项比较耗时的工作,应该通过发送Intent给Service,由Service来完成。这里不能使用子线程来解决,因为BroadcastReceiver的生命周期很短,子线程可能还没有结束BroadcastReceiver就先结束了。BroadcastReceiver一旦结束,此时BroadcastReceiver的所在进程很容易在系统需要内存时被优先杀死,因为它属于空进程(没有任何活动组件的进程)。如果它的宿主进程被杀死,那么正在工作的子线程也会被杀死。所以采用子线程来解决是不可靠的。 public class IncomingSMSReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //发送Intent启动服务,由服务来完成比较耗时的操作 Intent service = new Intent(context, XxxService.class); context.startService(service); } }
  • 100. 黑马训练营 www.itheima.com广播接收者除了短信到来广播Intent,Android还有很多广播Intent,如:开机启动、电池电量变化、时间已经改变等广播Intent。 接收电池电量变化广播Intent ,在AndroidManifest.xml文件中的节点里订阅此Intent: 接收开机启动广播Intent,在AndroidManifest.xml文件中的节点里订阅此Intent: 并且要进行权限声明:
  • 101. 黑马训练营 www.itheima.com拦截外拔电话向外拨打电话时系统会发出一个有序广播,虽然该广播最终会被拔号器里的广播接收者所接收并实现电话拔打,但我们可以在广播传递给拔号广播接收者之前先得到该广播,然后清除传递给拔号广播接收者的电话号码,在拔号广播接收者接收到该广播时,由于电话号码为null,因此取消电话拔打。 public class OutgoingCallReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { setResultData(null); //清除电话,广播被传给系统的接收者后,因为电话为null,取消电话拔打 // 同样如果你想修改外拔的电话号码,可以这样做 // String phone = getResultData();//得到外拔电话 // setResultData(“12593”+ phone);//在电话前面加上12593 } } 接收外拔电话广播Intent,在AndroidManifest.xml文件中的节点里订阅此Intent: 并且要进行权限声明:
  • 102. 黑马训练营 www.itheima.com服务--ServiceAndroid中的服务和windows中的服务是类似的东西,服务一般没有用户操作界面,它运行于系统中不容易被用户发觉,可以使用它开发如监控之类的程序。服务的开发比较简单,如下: 第一步:继承Service类 public class SMSService extends Service { } 第二步:在AndroidManifest.xml文件中的节点里对服务进行配置: 服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。使用startService()方法启用服务,访问者与服务之间没有关连,即使访问者退出了,服务仍然运行。使用bindService()方法启用服务,访问者与服务绑定在了一起,访问者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。 采用Context.startService()方法启动服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。
  • 103. 黑马训练营 www.itheima.com建立能与访问者进行相互通信的本地服务通过startService()和stopService()启动关闭服务。适用于服务和访问者之间没有交互的情况。如果服务和访问者之间需要方法调用或者传递参数,侧需要使用bindService()和unbindService()方法启动关闭服务。 采用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法,这个时候访问者和服务绑定在一起。 如果访问者要与服务进行通信,那么,onBind()方法必须返回Ibinder对象。如果访问者退出了,系统就会先调用服务的onUnbind()方法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法并不会导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果访问者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,调用该方法也会导致系统调用服务的onUnbind()-->onDestroy()方法。
  • 104. 黑马训练营 www.itheima.com服务的生命周期回调方法服务的生命周期跟启动服务的方法有关: 当采用Context.startService()方法启动服务,与之有关的生命周期方法 onCreate() onStart()  onDestroy() onCreate()该方法在服务被创建时调用,该方法只会被调用一次,无论调用多少次startService()或bindService()方法,服务也只被创建一次。 onStart() 只有采用Context.startService()方法启动服务时才会回调该方法。该方法在服务开始运行时被调用。多次调用startService()方法尽管不会多次创建服务,但onStart() 方法会被多次调用。 onDestroy()该方法在服务被终止时调用。 当采用Context.bindService()方法启动服务,与之有关的生命周期方法 onCreate() onBind()  onUnbind()  onDestroy() onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。 onUnbind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用。 如果先采用startService()方法启动服务,然后调用bindService()方法绑定到服务,再调用unbindService()方法解除绑定,最后调用bindService()方法再次绑定到服务,触发的生命周期方法如下: onCreate()onStart()onBind()onUnbind()[重载后的方法需返回true]onRebind()
  • 105. 黑马训练营 www.itheima.com使用AIDL和远程服务实现进程通信 在Android中, 每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的。因此要传递对象, 需要把对象解析成操作系统能够理解的数据格式, 以达到跨界对象访问的目的。在JavaEE中,采用RMI通过序列化传递对象。在Android中, 则采用AIDL(Android Interface Definition Language:接口定义语言)方式实现。 AIDL是一种接口定义语言,用于约束两个进程间的通讯规则,供编译器生成代码,实现Android设备上的两个进程间通信(IPC)。AIDL的IPC机制和EJB所采用的CORBA很类似,进程之间的通信信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。由于进程之间的通信信息需要双向转换,所以android采用代理类在背后实现了信息的双向转换,代理类由android编译器生成,对开发人员来说是透明的。 实现进程通信,一般需要下面四个步骤:(请见页面下方备注栏)
  • 106. 黑马训练营 www.itheima.com进程间传递自定义类型参数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用来完成数据的序列化传递。 进程间传递自定义类型的实现过程请参见页面下方备注栏:
  • 107. 黑马训练营 www.itheima.com监听电话呼叫状态要实现电话窃听,需要监听电话的状态,方法如下: /* 取得电话服务 */ 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中添加权限:
  • 108. Android没有对外公开结束通话的API,如果需要结束通话,必须使用AIDL与电话管理服务进行通信,并调用服务中的API实现结束通话,方法如下: 1> 从Android的源代码中拷贝以下文件到项目中: com.android.internal.telephony包下的ITelephony.aidl android.telephony包下的NeighboringCellInfo.aidl 注意:需要在项目中建立对应的包名存放上述两个aidl文件, 如右图所示。开发工具会在gen目录下自动生成ITelephony.java 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中添加权限: 黑马训练营 www.itheima.com结束通话--实现黑名单拦截
  • 109. 黑马训练营 www.itheima.com音频采集你可以使用手机进行现场录音,实现步骤如下: 第一步:在功能清单文件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/itcast.amr"); recorder.prepare();//预期准备 recorder.start(); //开始刻录 ... recorder.stop();//停止刻录 recorder.reset(); //重设 recorder.release(); //刻录完成一定要释放资源
  • 110. 黑马训练营 www.itheima.com音乐播放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; } });
  • 111. 黑马训练营 www.itheima.com使用SoundPool播放音效 在Android开发中我们经常使用MediaPlayer来播放音频文件,但是MediaPlayer存在一些不足,例如:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的游戏开发中。 在游戏开发中我们经常需要播放一些游戏音效(比如:子弹爆炸,物体撞击等),这些音效的共同特点是短促、密集、延迟程度小。在这样的场景下,我们可以使用SoundPool代替MediaPlayer来播放这些音效。 SoundPool(android.media.SoundPool),顾名思义是声音池的意思,主要用于播放一些较短的声音片段,支持从程序的资源或文件系统加载。与MediaPlayer相比,SoundPool的优势在于CPU资源占用量低和反应延迟小。另外,SoundPool还支持自行设置声音的品质、音量、播放比率等参数,支持通过ID对多个音频流进行管理。 就现在已知的资料来说,SoundPool有一些设计上的BUG,从固件版本1.0开始有些还没有修复,我们在使用中应该小心再小心。相信将来Google会修复这些问题,但我们最好还是列出来:   1. SoundPool最大只能申请1M的内存空间,这就意味着我们只能用一些很短的声音片段,而不是用它来播放歌曲或者做游戏背景音乐。   2. SoundPool提供了pause和stop方法,但这些方法建议最好不要轻易使用,因为有些时候它们可能会使你的程序莫名其妙的终止。建议使用这两个方法的时候尽可能多做测试工作,还有些朋友反映它们不会立即中止播放声音,而是把缓冲区里的数据播放完才会停下来,也许会多播放一秒钟。   3. SoundPool的效率问题。其实SoundPool的效率在这些播放类中算是很好的了,但是有的朋友在G1中测试它还是有100ms左右的延迟,这可能会影响用户体验。也许这不能管SoundPool本身,因为到了性能比较好的Droid中这个延迟就可以让人接受了。   在现阶段SoundPool有这些缺陷,但也有着它不可替代的优点,基于这些我们建议大在如下情况中多使用SoundPool:1.应用程序中的声效(按键提示音,消息等)2.游戏中密集而短暂的声音(如多个飞船同时爆炸)
  • 112. 黑马训练营 www.itheima.com视频播放在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();//释放资源
  • 113. 黑马训练营 www.itheima.com使用摄像头拍照在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();//释放摄像头
  • 114. 黑马训练营 www.itheima.com音视频采集第一步:在功能清单文件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/itcast.3gp"); recorder.setPreviewDisplay(surfaceView.getHolder().getSurface()); recorder.prepare();//预期准备 recorder.start();//开始刻录 ... recorder.stop();//停止刻录 recorder.release(); //刻录完成一定要释放资源
  • 115. 黑马训练营 www.itheima.comAndroid的状态栏通知(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;//发出默认声音 Intent openintent = new Intent(this, OtherActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, openintent, 0);//当点击消息时就会向系统发送openintent意图 notification.setLatestEventInfo(this, “标题”, “我是内容", contentIntent); mNotificationManager.notify(0, notification);//第一个参数为自定义的通知唯一标识
  • 116. 黑马训练营 www.itheima.com对话框通知(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.itcast.cn/");//打开链接 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()这些方法,他们的返回值都是当前对话框对象。
  • 117. 黑马训练营 www.itheima.com创建带单选项列表的对话框下面代码将打开一个如右上图所示的选项列表对话框: 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代表不选择任何选项。
  • 118. 黑马训练营 www.itheima.com创建带多选项列表的对话框下面代码将打开一个如右下图所示的多选项列表对话框: 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();//显示对话框
  • 119. 黑马训练营 www.itheima.com进度对话框(ProgressDialog)效果图: 使用代码ProgressDialog.show(ProgressDialogActivity.this, "请稍等", "数据正在加载中...", true);创建并显示一个进度对话框。 调用setProgressStyle()方法设置进度对话框风格。有两种风格: ProgressDialog.STYLE_SPINNER 旋体进度条风格 (为默认风格) ProgressDialog.STYLE_HORIZONTAL 横向进度条风格
  • 120. 黑马训练营 www.itheima.com单选框(RadioButton)效果图: 要完成单选框显示,我们需要使用到RadioGroup和RadioButton(单选框),RadioGroup用于对单选框进行分组,相同组内的单选框只有一个单选框能被选中。(例子代码请见下方备注栏) RadioGroup.check(R.id.dotNet);将id名为dotNet的单选框设置成选中状态。 (RadioButton) findViewById(radioGroup.getCheckedRadioButtonId());//获取被选中的单选框。 RadioButton.getText();//获取单选框的值 调用setOnCheckedChangeListener()方法,处理单选框被选择事件,把RadioGroup.OnCheckedChangeListener实例作为参数传入
  • 121. 黑马训练营 www.itheima.com多选框(CheckBox)效果图: 每个多选框都是独立的,可以通过迭代所有多选框,然后根据其状态是否被选中再获取其值。 CheckBox.setChecked(true);//设置成选中状态。 CheckBox.getText();//获取多选框的值 调用setOnCheckedChangeListener()方法,处理多选框被选择事件,把CompoundButton.OnCheckedChangeListener实例作为参数传入
  • 122. 黑马训练营 www.itheima.com下拉列表框(Spinner)效果图: Spinner.getItemAtPosition(Spinner.getSelectedItemPosition());获取下拉列表框的值 调用setOnItemSelectedListener()方法,处理下拉列表框被选择事件,把AdapterView.OnItemSelectedListener实例作为参数传入
  • 123. 黑马训练营 www.itheima.com下拉列表框—采用javabean作为Adapter元素效果图: 很多时候显示在下拉列表框的值并不是希望得到的值,如果要做一个联系人下拉列表框,列表框列出的是联系人的姓名,因为姓名有可能相同,所以我们希望得到的值应该为该联系人的id,要实现这种需求我们需要自定义Adapter,当然自定义Adapter需要我们编写一小段代码,如果我们不想编写Adapter,又能实现我们的需求,那是最好不过的了。通过观察ArrayAdapter中getView(int position, View convertView, ViewGroup parent)的内部代码发现,如果为ArrayAdapter指定的实际泛型参数类型没有实现CharSequence(字符串)接口,将会调用该类型对象的toString()向下拉列表框输出显示值。利用这个特点我们可以重写javaBean的toString()向下拉列表框提供显示值。
  • 124. 黑马训练营 www.itheima.com下拉列表框--自定义选项界面样式效果图: Spinner.getItemAtPosition(Spinner.getSelectedItemPosition());获取下拉列表框的值 调用setOnItemSelectedListener()方法,处理下拉列表框被选择事件,把AdapterView.OnItemSelectedListener实例作为参数传入
  • 125. 黑马训练营 www.itheima.com拖动条(SeekBar)效果图: SeekBar.getProgress()获取拖动条当前值 调用setOnSeekBarChangeListener()方法,处理拖动条值变化事件,把SeekBar.OnSeekBarChangeListener实例作为参数传入
  • 126. 黑马训练营 www.itheima.com菜单(Menu)效果图: 重写Activity的onCreateOptionsMenu(Menu menu)方法,该方法用于创建选项菜单,在用户按下手机的“Menu”按钮时就会显示创建好的菜单,在onCreateOptionsMenu(Menu menu)方法内部可以调用Menu.add()方法实现菜单的添加。 重写Activity的onMenuItemSelected()方法,该方法用于处理菜单被选择事件 通过手机上提供的“MENU”按钮可以打开菜单,如果希望通过代码打开菜单,可以调用Activity的openOptionsMenu()方法。
  • 127. 黑马训练营 www.itheima.com进度条(ProgressBar) 在布局xml文件中添加进度条代码: 在代码中操作进度条: ProgressBar.setMax(100);//设置最大刻度 ProgressBar.setProgress(0);//设置进度条的当前刻度,如果进度条的最大刻度为100,当前刻度为50,进度条将进行到一半。
  • 128. 黑马训练营 www.itheima.com输入内容自动完成文本框(AutoCompleteTextView )AutoCompleteTextView和EditText组件类似,都可以输入文本。 但AutoCompleteTextView组件可以和一个字符串数组或List对象 绑定,当用户输入两个及以上字符时,系统将在 AutoCompleteTextView组件下方列出字符串数组中所有以输入 字符开头的字符串,这一点和www.google.com的搜索框非常相似, 当输入某一个要查找的字符串时,google搜索框就会列出以这个 字符串开头的最热门的搜索字符串列表。 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); }
  • 129. 黑马训练营 www.itheima.com多次输入-内容自动完成文本框(MultiAutoCompleteTextView)除了AutoCompleteTextView控件外,我们还可以使用MultiAutoCompleteTextView控件来完成连续输入的功能。也就是说,当输入完一个字符串后,在该字符串后面输入一个逗号(,),在逗号前后可以有任意多个空格,然后再输入一个字符串,仍然会显示自动提示列表。 使用MultiAutoCompleteTextView时,需要为它的setTokenizer方法指定MultiAutoCompleteTextView.CommaTokenizer类对象实例, 该对象表示采用逗号作为输入多个字符串的分隔符。 < MultiAutoCompleteTextView android:layout_width="fill_parent“ android:layout_height="wrap_content“ 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());}
  • 130. 黑马训练营 www.itheima.comandroid样式和主题(style&theme) android中的样式和CSS样式作用相似,都是用于为界面元素定义显示风格,它是一个包含一个或者多个view控件属性的集合。如:需要定义字体的颜色和大小。 在CSS中是这样定义的: 可以像这样使用上面的css样式:
    传智播客
    在Android中可以这样定义样式: 在res/values/styles.xml文件中添加以下内容 在layout文件中可以像下面这样使用上面的android样式:
  • 131. 黑马训练营 www.itheima.comandroid样式和主题(style&theme)
  • 132. 黑马训练营 www.itheima.comandroid样式和主题(style&theme) android中主题也是用于为应用定义显示风格,它的定义和样式的定义相同,如下: 上面“?android:windowNoTitle”中的问号用于引用在当前主题中定义过的资源的值。下面代码显示在AndroidManifest.xml中如何为应用设置上面定义的主题: ...... 除了可以在AndroidManifest.xml中设置主题,同样也可以在代码中设置主题,如下: setTheme(R.style.itcastTheme); 尽管在定义上,样式和主题基本相同,但是它们使用的地方不同。样式用在单独的View,如:EditText、TextView等;主题通过AndroidManifest.xml中的用在整个应用或者某个 Activity,主题对整个应用或某个Activity进行全局性影响。如果一个应用使用了主题,同时应用下的view也使用了样式,那么当主题和样式属性发生冲突时,样式的优先级高于主题。 另外android系统也定义了一些主题,例如:,该主题可以让Activity看起来像一个对话框,还有透明主题:@android:style/Theme.Translucent 。如果需要查阅这些主题,可以在文档的referenceandroid-->R.style 中查看。
  • 133. 黑马训练营 www.itheima.com编码实现软件界面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); } }
  • 134. 黑马训练营 www.itheima.com使用网页开发软件界面因为android软件开发分工目前还没有细化,程序员往往需要负责软件界面的开发,虽然软件的界面图片已经由美工设计好了,但如果使用layout技术把软件做成如图片所示的界面确实很困难,而且也比较耗时。Android通过WebView实现了JS代码与Java代码互相通信的功能,使的android软件的界面开发也可以采用HTML网页技术,这样,广大网页美工可以参与进android软件的界面开发工作,从而让程序员从中解脱出来。
  • 135. 黑马训练营 www.itheima.com动画(Animation)Android提供了2种动画: 1> Tween动画,通过对 View 的内容进行一系列的图形变换 (包括平移、缩放、旋转、改变透明度)来实现动画效果。动画效果的定义可以采用XML来做也可以采用编码来做。Tween动画有4种类型: 2> Frame动画,即顺序播放事先做好的图像,跟放胶片电影类似。开发步骤: (1)把准备好的图片放进项目res/ drawable下。 (2)在项目的res目录下创建文件夹anim,然后在anim文件夹下面定义动画XML文件,文件名称可以自定义。当然也可以采用编码方式定义动画效果(使用AnimationDrawable类)。 (3)为View控件绑定动画效果。调用代表动画的AnimationDrawable的start()方法开始动画。 动画的类型Xml定义动画使用的配置节点编码定义动画使用的类渐变透明度动画效果 AlphaAnimation渐变尺寸缩放动画效果 ScaleAnimation画面位置移动动画效果 TranslateAnimation画面旋转动画效果RotateAnimation
  • 136. 黑马训练营 www.itheima.com传感器的使用传感器类型:方向、加速度(重力)、光线、磁场、距离(临近性)、温度等。 方向传感器: 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 一般对于屏幕方向自动旋转使用,相对节省电能和逻辑处理,一般游戏开发中我们不使用。
  • 137. 黑马训练营 www.itheima.comNinePatch图片NinePatch是一种很有用的PNG图片格式,它可以在特定区域随文字大小进行缩放。如下: 从上图可以看到,背景图片的中间区域会随着文字的大小进行缩放。背景图片是一张NinePatch图片。 NinePatch图片可以使用android自带的draw9patch工具来制作,该工具在SDK安装路径的tools目录下。执行该工具,然后点击“File”->“open 9-path”打开一张用于制作NinePatch图片的原来图片。在画布的上方和左方的边上画线指定缩放区域, 勾选“Show patches”可显示画定的区域,绿色 为固定大小区域,红色为缩放区域,文字会摆放在红色 区域。制作完后,点击“File” “save 9-path”保存 图片,draw9patch工具会自动为图片加上*.9.png后缀。 把制作好的图片拷贝进项目的res/drawable目录,然后 编写代码。如下:
  • 138. 黑马训练营 www.itheima.com关闭应用当应用不再使用时,通常需要关闭应用,可以使用以下三种方法关闭android应用: 第一种方法:首先获取当前进程的id,然后杀死该进程。 android.os.Process.killProcess(android.os.Process.myPid()) 第二种方法:终止当前正在运行的Java虚拟机,导致程序终止 System.exit(0); 第三种方法:强制关闭与该包有关联的一切执行 ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); manager.restartPackage(getPackageName());
  • 139. 黑马训练营 www.itheima.com判断SIM卡属于哪个移动运营商见备注栏: 在文件AndroidManifest.xml中添加权限
  • 140. 黑马训练营 www.itheima.com从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卡信息的添删改查操作。 内部实现源代码参见备注栏:
  • 141. 黑马训练营 www.itheima.com删除呼叫记录在文件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)
  • 142. 黑马训练营 www.itheima.com 在应用中安装其他程序首先需要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);
  • 143. 黑马训练营 www.itheima.com 如何反编绎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源代码。
  • 144. 如何防止我们的代码被反编译由于apk是Android虚拟机加载的,它有一定的规范,加密apk后Dalvik无法 识别apk了。完全避免是不可能的,总有人能够破解你的代码。但是有几种 方式来提高被反编译取代码的难度。 1 关键代码使用jni调用本地代码,用c或者c++编写,因此相对比较难于反 编译 2 混淆java代码。混淆是不改变代码逻辑的情况下,增加无用代码,或者重 命名,使反编译后的源代码难于看懂。 网上开源的java代码混淆工具较多,一般是用ant的方式来编译的