11.0《2015最新Android基础入门教程》完结散花~

最后更新于:2022-04-01 05:30:33

## 引言: 从六月底就开始编写这套教程,历时将近五个多月,今天终于写完了,全套教程正文部分148篇, 十大章,从基本UI控件到四大组件,Intent,Fragment,事件处理,数据存储,网络编程,绘图与动画, 多媒体,系统服务等都进行了详细的讲解!代码都是都是在Android Studio上进行编写的,全文 采用Markdown,行文结构清晰,还结合了实际开发中一些常见的问题进行了剖析...由于个人能力的局限, 虽然竭尽全力,但是难免还有有一些错误纰漏,望读者海涵指出,万分感激!在写这套教材的过程中, 感触良多,借着完结散花这最后一节一吐而快,也算是暂时告别自己博客生涯的一笔吧... ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79a391908.jpg) 一吐而快~ * * * ## 1.此套教程的由来 记得那是在五月份的某一天晚上,刚和舍友打完撸啊撸,玩起手机来。不经意的我加了w3c鸟巢的 公众号,然后看了下推送过来的文章,感觉有点意思,于是乎就到度娘上搜了下"w3c鸟巢"。发现有 个菜鸟教程的栏目,然后里面的教程大部分都是Web类的基础教程,而我看到了移动端的教程,上面 有着"学习Android"!这样一个教程![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ae1ebc7.jpg),作为一个搞Android的,按照故事情节,我肯定会马上 去点开这个链接,然后发生点什么事吧...然而,我并没有点...所以故事到这里就结束了,哈哈... 当然,最后还是点了,不过在这个期间和舍友下去喝了碗糖水而已~因为年代久远,但是教程里的内 容我都已忘记,但我现在还记得,在我的柜子底有一本《Android疯狂讲义》,大学买的第一本 编程书,哈哈,可惜看了100来页我已经放弃了,一本中文版的API文档哈...也就是因为这本书,才 会小猪Android入门之路的专栏,当时抱着试一试的心情,加了w3c大师姐的微信,然后问她需不需 要一个写Android基础教程的,接着把小猪入门之路的链接发给他了,然后大师姐貌似非常的高兴, 然后又问卖不卖版权之类的, **卖版权**![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c423a14.jpg)?那不是有钱收咩? 作为一个苦逼学生狗,写点东西有钱收,想想还有点小激动呢,结果兴奋了一晚上,脑子里想了 很多...后来也不知道自己是怎么想的,就跟FK(w3c鸟巢的站长)聊了下, 然后就决定在w3c鸟巢的菜鸟教程上写一套Android的基础教程,**免费**,嗯,没错,不收一分钱, 前提是教程不用于商业用途,原因可能是被FK的分享的精神所渲染吧,也可能是自己真的想去写 一套Android教程吧,大部分大牛没时间或者不屑于去写基础入门教程,那么就让我这个渣渣来写 吧!为后面的初学者铺铺路也好嘛~ 接下来就是用百度脑图来构思入门系列要讲解的内容,学了下Markdown语法,然后就开搞,一开始 是不想在coder-pig上写的,毕竟上面有很多太监了的教程。然后开了个小号,打算在上面写这套 新教程,但是访问量却惨不忍睹,假如你是一个写博客的,看到自己花了很多时间写出来的东西, 却没人看的时候,心里肯定不舒服是吧...后来还是默默地搬回了coder-pig上,然后把第一章写 完,也开始在w3c鸟巢上发布了!接着每天就开始下面这种**一成不变的枯燥的生活**: 每天上班,一有时间就构思今天写什么知识点,写个什么样有趣的例子,然后晚上5点半下班后, 去吃个饭,然后就回来埋头苦写,每天晚上基本上都是我锁门的,一般十点半左右走吧,记得 最晚一次写得太嗨没注意时间写到12点半,写完看了下时间,卧槽,十二点半!!! ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c433981.jpg) 吓得我赶忙收拾东西,拔腿就跑,因为园区这边好像是12点就关后门的,一到那里发现门是关 着的,心理顿时凉了一大半,妈蛋,难道今晚真的要睡公司么...后来走进一看才发现门是虚掩的, 并没锁,最后还是顺利地回到了宿舍...周六日一般也没什么节目,都是回公司码字,偶尔 天气好就去跟别人打打羽毛球,大部分时间还是花在码字上,就这样坚持了五个多月,这套教程 也总算完结了~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c448edd.jpg) 此刻的心情,有点小高兴,也有点小激动,但更多的却是一种解脱,总算写完了~~~ 为何是解脱,不急,还请听我娓娓道来... * * * ## 2.扒一扒我的一些情况 * * * 不用到群里问猪神在那里高就,月薪多少,做我徒弟之类的话了,现在就扒一扒自己的一些情况吧! 今年的应届毕业生(15届),学校是北京理工大学珠海学院(北理珠),目前在南方软件园这边工作,是 一枚**Android实习生**,月薪也只有**3K**,五险一金什么鬼都没有!嗯,你没看错,我是一名 **3K实习生**,或许你会觉得我在开玩笑,但这就是事实,因为自己大学时候的任性,我现在还有 两门科目没有过:高数上和下,所以还没拿到毕业证...很失望是吧,还以为写这套教材的是哪位 大牛,结果是一个实习生么,哈哈~ 嗯,说下自己的当前的Android技术水平吧: 中下,或者说中下也达不到,可以单独完成小型的项目!但是架构什么的,屎一比,根本不考虑 复用之类的,可以说是任意拼凑起来的垃圾,很多新兴起的技术,听过但是没有花时间去研究... 接着说说自己的**工作经历**吧: ### 2015.2 学校春节招聘会,找的第一份实习,在拱北跨境工业区那边,一家外包公司,说是公司 还不如说是工作室,加起来就那么7个人,后来还跑了个HR。在这个公司呆了一个来月,收获就是: 学会了去看官方的API文档,而非啃李刚;学会了改Hosts;知道了Fragment的用法; 写了华仔天地(刘德华粉丝俱乐部APP)的UI;各种打杂; 他们有一套自己的东西,其实就是将一些常用到的功能丢到一个Jar包里,比如图像异步加载, 图片大小的动态计算等...要什么功能问后面的,没错,没文档...所有的APP都是那个套路, 可能外包公司都是这样吧,只在乎结果而不在乎过程,另外最让我接受不了的是测试, 叫我和美工在那里划屏,只要程序不crash就好了,这就叫测试... ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c457858.jpg) 于是乎,我离开了这家公司,此时我实习2.8k,转正3.2k! ### 2015.4 接着我又找了另一份工作,在清华科技园那边,这就不是一家外包公司了,他们主要是 做安防和智能家居类的,氛围还是不错的,偶尔会有技术问题的撕比,周五下午还有技术交流, 而且给我配了个新的电脑和显示器,感觉自己在这里呆肯定会很嗨皮!第一个月看看文档什么的, 看看要接手的项目什么的,小日子还是过得挺滋润的,可是好景不长,做了3年的那个老员工要走了! 他手上的两个项目都丢给了我,而且我还要开始搞另一个新的项目,这没什么,勉勉强强还可以 扛下来,但是那两个接手的项目有个要改,而且要出版本,我连代码都还没来得及熟悉...怎么玩 得过来啊,自己做不过来,又不想耗时间,到时期限到了我什么都没搞出来,这样还拖累了别人! 记得想辞职前的那周过得非常的压抑,机缘巧合,好像是周三的下午吧,收到了现在公司HR打来 的电话,然后和现在的经理电话面试了下,问了一些Android基础的东西,聊得还蛮嗨的,然后约 个时间见见面,然后周五就过来面试了,再接着聊了一下现在公司的一些情况,第一感觉公司 环境还可以吧,位置都比较宽敞,然后跟他说了下我还没拿到毕业证的事,能不能转正, 可能是他当时口爽,说没什么跟人事那边说下就好...然而我在这里蹲半年了,还是实习... 然后周一回公司提交了辞职申请,然后离开了第二家公司,在这个公司的一个月,扩展了一下 自己的视野,知道了NDK和视频编解码这些东西~此时我试用3.8k,转正4.2k! ### 2015.5 嗯,辞去第二间公司的工作后,在学校嗨了一个星期,随手把自己的毕设给弄 完了,被迫分割成两个应用的毕设:海绵表表和一起啪啪啪,现在看来那两个自己写出来的东西, 无法直视,后来把毕设卖了,200块... 然后周一的时候就来到了现在的这家公司,又是接手项目,原来这里的那个Android开发的大牛 要跳到魅族去,第一次感觉到大牛的气息,假如他并没有走而是继续呆着多好呢? 或许我此时又会是另外一种不同的结局了是吧~从SVN过渡到Git,从图形化界面过渡到命令行; 知道了注解,RxJava,okhttp,github,多渠道打包等等,愉快地相处了一周后,大牛走了, 接下来就是我自己看项目了,感觉就像来到一个新大陆一样,很多东西我以前都没见过, 就这样嗨了将近一个月,公司招到了另一名Android开发的,一开始听说是三年工作经验, 感觉有人带我飞了,然而事与愿违,在他身上我并没有感觉到一股大牛的气息,感觉可能是在 这个行业呆了三年吧,水平很一般,和自己比的话可能业务经验多一点吧,跟他讨论md他听都 没听过,Android Studio也不知道,其他的更不用说,记得有一次问他一个简单的控件怎么 自定义,他的回答是:网上找下改改就能用,我想问的是实现的思路,得到回答是:知道怎么用就好... 嗯,好吧!三年嘛,项目肯定是他来接手的啦,而经理丢给了我另外一个项目, 一个无人机上绑手机测量基站天线角度等信息,然后通过wifi显示到地面上的另一台手机上, 手机自身数据采集和数据传输到没什么,难点是**串口通信**(FTDI)的东西,手机 通过OTG线连单片机,完成指令收发,看着API文档撸了一个星期,连个最简单的Demo都写不出来 有发没收...同样的情况又持续了一个星期,好吧,写不出东西的感觉真的很不爽,后来没办法, 只能反编译别人的apk了,花了两天时间把别人apk里的代码抽取出最关键的部分,从6000多行 的代码变成500多行的代码,看到单片机上的收发信号灯闪烁,还蛮有成就感的! 可惜好景不长经理说要加个实时视频播放的,我真是....这玩意我都没搞过,怎么玩,于是到 Github上找了,几个开源的视频直播项目,后来还是找了WifiCarema作为研究项目,然后因为 h264库编译的问题纠结了差不多两个月,结果还是没有解决,结果项目外包给了北京那边的人 做,嗯,我的第一个项目就这样阉割了...接着做了一个很简单的小东西,再接着就到现在 就是一直在跟踪解决websocket的问题了~我司推送并不是用的第三方,而是自己用socketio 搭建的一个推送平台,用socketio的原因是三个平台都可以用一套嘛,iOS,Android,还有 web端,然后出现了漏掉报文或者收不到位置更新的问题,到现在还没找到问题发生的原因, 连问题都重现不了,我们这边一直测都没问题,一到客户手里就各种问题... 现在还在纠结这个问题中...来这里半年了,还是实习生,实习工资3k,毕业证起码要明年六月份才能 拿到,应该没得转正了,唉.. 嗯,上面就是我今年到现在的一些情况,前段时间去追梦网络面试,和面试官谈了谈自己 当前的一些情况,他说感觉我走了野路子,很多东西都走偏了,然后跟我说毕业这一年 很关键,以后成型了就难改了,然后又谈了一些架构的东西,嗯,第一次那么想进一家公司, 哪怕实习两个月也好,嗯,很遗憾,结果并没有拿到offer,不过也很感谢全齐大神给自己 上了一堂课,总算知道自己接下来要去学点什么~然后又面了两家,没什么感觉,不是自己 向往的类型,最后投了一波魅族实习生,哈哈,连面试的机会都没有,这是第一次,估计 HR连简历都没看到吧~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c466540.jpg) 以上就是我的一些个人情况的描述了,我真的是**只有3K的实习狗**,所以群里各位10k的 大老爷们,别逢年过节就叫我这个穷比发红包了... ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c4769f2.jpg) * * * ## 3.一些自学心得以及资源分享 > 怎么学Android,这可能是初学者问得最多的问题了,通过上面你也知道了小猪有多屎, 所以下面这些都是鄙人对于自学的一些浅显的看法而已,不喜请喷~ ### 1)看书 **入门推荐的书**: * 《**第一行代码**》:这本就不用说啦,郭霖大神写的书,入门必备 * 《**Android群英传**》:这本是医生(徐宜生)写的,嘿嘿,双11买的,今天刚收到, 翻了下,感觉内容还是蛮简单,适合看完第一本书,或者会点Android的~ 可能有的朋友会说,还有李刚Android疯狂讲义咧...嗯,买来当字典查也可以, 但是感觉看上面两本会让你更快入门,另外,在看第一行代码的时候,你也可以配合 着小猪写的基础入门教程一同服用,效果更佳~ **进阶推荐的书**: 也是接下来自己想入手的几本书: * 《**Android源码设计模式解析与实战**》: 何红辉(Simple哥),关爱民(爱哥)两人的大作,既可以学 习到设计模式,也可以体会到Android中蕴含的一些设计思想! * 《**Android开发艺术探索**》:任玉刚,侧重于Android知识的体系化和系统工作机制的分析 * 《**深入解析Android 5.0系统** 》:剖析了最新Android 5.0 系统主要框架的原理和具体实现~ 上述几本书我都还没摸过(还没入手),都是广受好评的几本书~这里也推荐下! ### 2)看视频 网上关于Android的视频教程有很多,这里分享下基神力荐的黑马教程吧: [黑马28期Android全套视频无加密完整版](http://pan.baidu.com/s/1jGASiqy):密码:h7jz [52期不加密版](http://pan.baidu.com/s/1dDBIqlz):密码:zve8 当然下面这些视频学习网站也很不错,也推荐下 [慕课网](http://www.imooc.com/) [极客学院](http://www.jikexueyuan.com/) [麦子学院](http://www.maiziedu.com/) ### 3)看别人的技术博客 * [CodeKK](http://a.codekk.com/) —— 专注于开源项目源码解析及优秀开源项目的分享 * [Trinea](http://www.trinea.cn/) —— 性能优化、源码解析 * [老罗的Android之旅](http://blog.csdn.net/Luoshengyang) —— Android系统源代码分析 * [开发技术前线](http://www.devtf.cn/) —— 《Android源码设计模式》作者 Mr.Simple 维护的社区网站 * [爱哥](http://blog.csdn.net/aigestudio) ——《Android源码设计模式》作者 关爱民 * [任玉刚](http://blog.csdn.net/singwhatiwanna) —— 《Android开发艺术探索》作者 CSDN博客 * [郭霖](http://blog.csdn.net/guolin_blog) —— 《第一行代码》作者 CSDN博客 * [鸿洋](http://blog.csdn.net/lmj623565791) —— CSDN 博客专家 * [胡凯](http://hukai.me/) —— 专注性能优化 * [张明云](http://www.jianshu.com/users/e6885381f7d4/latest_articles) —— Android学习之路 * [Drakeet](http://drakeet.me/) —— 贝壳单词APP开发者 * [徐宜生](http://blog.csdn.net/eclipsexys) —— 《Android群英传》作者 * [代码家](http://blog.daimajia.com/) —— 著名博主 * [脉脉不得语](http://www.inferjay.com/blog/categories/androiddevweekly/) —— 著名博主 * [高建武](http://www.jianshu.com/users/FK4sc4/latest_articles) —— 专注性能优化,简书著名博主 * [程序亦非猿](http://yifeiyuan.me/)—— 简书著名博主 * [廖祜秋liaohuqiu_秋百万](http://liaohuqiu.net/) —— 淘宝职员 * [hi大头鬼hi](http://blog.csdn.net/lzyzsd) —— 对RxJava有较深的研究 * [阳春面](http://www.jianshu.com/users/nqobaq/latest_articles) —— 简书著名博主 * [夏安明](http://blog.csdn.net/xiaanming) —— CSDN 博客专家 * [兰亭风雨](http://blog.csdn.net/ns_code) —— CSDN 博客专家 * [赵凯强](http://blog.csdn.net/zhaokaiqiang1992) —— CSDN 博客专家 * [qinjuning](http://blog.csdn.net/qinjuning) —— CSDN 博客专家 * [工匠若水](http://blog.csdn.net/yanbober) —— CSDN 博客专家 * [张兴业](http://blog.csdn.net/xyz_lmn) —— CSDN 博客专家 * [Coder-pig](http://blog.csdn.net/coder_pig) —— CSDN 博客专家,最佳入门专栏 * [Keegan小刚](http://keeganlee.me/) —— 分享了多篇Android样式的文章 * [郑海波](http://blog.csdn.net/NUPTboyZHB/) —— CSDN博主,文章大多与自定义控件相关 * [吴小龙同学](http://wuxiaolong.me/) —— 分享了多篇关于AndroidDesignSupportLibrary的文章 * [全速前行](http://blog.csdn.net/lincyang) —— CSDN 博客专家,主讲实战技巧和平常遇到的问题 ### 4)高质量Android社区 > * [Stackoverflow](http://stackoverflow.com/questions/tagged/android) —— 国外著名的问答社区 > * [V2ex](https://www.v2ex.com/go/android) —— V2ex Android板块 > * [Android 开发技术周报](http://www.androidweekly.cn/) —— 长期更新最新前言资讯 > * [开发技术前线](http://www.devtf.cn/) —— 《Android源码设计模式》作者 Mr.Simple 维护的社区网站 > * [泡在网上的日子](http://www.jcodecraeer.com/) —— 大量第三方控件基地 > * [开源中国](http://www.oschina.net/android) —— OsChina > * [23code](http://www.23code.com/) —— android经典开源代码分享 > * [App开发者](http://www.aswifter.com/) —— 分享Android/IOS/Swift开发和互联网内容 > * [JavaApk.com](http://www.javaapk.com/) —— 安卓demo聚集地,部分源码需购买VIP > * [DevStore](http://www.devstore.cn/code/list/ft85-pn1-or0.html) —— 各种Demo,以及第三方服务 ### 5)官方学习网站/Wiki > * [Android Developer](http://developer.android.com/) > * [Android Developer(无需梯子)](http://androiddoc.qiniudn.com/index.html) > * [Android Training 中文版](http://hukai.me/android-training-course-in-chinese/index.html) > * [Material Design 中文版](http://wiki.jikexueyuan.com/project/material-design/) > * [Android Weekly 中文版](http://wiki.jikexueyuan.com/project/android-weekly/) > * [极客学院 Wiki](http://wiki.jikexueyuan.com/) ### 6)代码/项目下载 >  嗯,大部分时间我都会选择到Github上面找,有很多开源的第三方,下面这个务必Star: > > [Android 开源项目分类汇总](https://github.com/Trinea/android-open-project) > > 然后笔者也分享下以前在某宝花了50多块买的一些代码吧: > > [5000套Android源码](http://pan.baidu.com/s/1sjQSP6d) 密码:6we6 [3175套iOS源码](http://pan.baidu.com/s/1mgpju0w) 密码:53v9 > > 上面的这套代码很多都是重复的,而且大部分都是基于Eclipse,涵括的还是比较广的,可以一下! ### 7)梯子工具 > 嗯,假如你不想经常改hosts或者不想买vpn,但是想用Google的话,那么你可以使用蓝灯(Lantern)~ 自己搜"Lantern"下载吧~ ### 8)一些其他的碎碎念: 嗯,上面的资源大部分来自于:[Android学习资源网站大全](https://github.com/zhujun2730/Android-Learning-Resources),请务必Star!!!后续如果 有什么新的资源都会在上面进行更新,也欢迎大家share自己的一些收藏,上面的内容是小猪 群里的第一大手——基神所写,当然还有B神和曹神,街神等,这里非常感谢各位一直以来对我的 一些指导以及帮助~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c48583e.jpg) 不知道你看到上面的资源是不是,收藏收藏,买买买,下下下~ 我想说的是,收藏了不去看,只是一个Url而已;下载了不去看,只是一堆数据而已; 买了书不去看,也只是一沓纸!不要让你自己只是看起来很忙很努力的样子, 装比给谁看?学到手的东西才是自己的,很喜欢这样一句话: "**技术之路最公平也最残酷的原因是:没有捷径,需要日积月累的积累,以及对技术持久的热情。**" 还记得很久之前看的锤子科技的射角设计总监罗子雄仔tedx上演讲的: "如何成为一名优秀的设计师"说过的这么一段话: **格拉德威尔在《异类》一书中指出:"人们眼中的天才,并非卓越非凡,而是付出了持续不断的努力, 一万小时的锤炼是任何人从平凡变成超凡的必要条件。"一万小时,也就是说你每天工作8小时的时间, 每周工作5天,你需要5年。你无需天才,无需智商过人,无需三头六臂,无需头上长角,你只需要持 续的、坚持的努力,有正确的方法,就能够在设计领域,一个专业中独当一面。** 尽管他讲的是设计,但是很多东西都是相通的,嘿嘿,无情地上了一大碗鸡汤~ 总结下自学,无非: **多看书,看博客,做项目,看源码,不断的总结反思,让自己所学的东西所学的东西结构化!** * * * ## 4)一些答疑 下面是一些读者经常问到的问题,下面统一答复下: **1**.**我是以前学XX的或者我不是搞编程的,我想来学Android,能学好不?之类的问题!** 答:前段时间在医生(徐宜生)的新浪微博看到,一位65岁的大爷,到他的公司向他请问 Android Studio,看到这里,你觉得上面的问题是问题吗? **2**.**XXX报错了?怎么办之类的问题** 答:这种最频繁,其实很多都可以在度娘或者谷哥上找到答案,这么多人搞Android难道就 你一个人出现过这样的问题吗?或者到Stackoverflow上提问等,先自己搜过思考过,再去 问别人!!!而且别人也没有回答你的义务,别搞得好像别人不回答你的问题就很什么, 然后就恶言相向!注意问问题的技巧,整理语言,发log,出错位置代码等! **3**.**想加小猪做好友,为什么我拒绝了?** 答:不知道你在哪看到了我的QQ,然后看了我写的东西,就迫切的想加我为好友, 我想问,加了,然后呢?问问题更方便了?刚开始加我的我都会家,一般都是问问题, 我每次都会很耐心的解答,然后就开始依赖我了,一出问题就找我...一个两个没什么, 慢慢地人越来越多,我每天的时间都基本用在回答问题上了,结果自己一天下来什么都没做成... 不是说小猪高冷或者看不起初学者之类的,我也有自己的事要做,希望各位可以体谅下! 有问题,可以到群里问,管理们都是很热心的,当然,前提是你的问题别一百度就可以 找到的...别做伸手党!!! **4**.**基础入门教程写完,那么什么时候开始写进阶教程?** 答:大家对基础入门教程的反馈都觉得写的不错,也受到了很多的好评,表扬,很感谢~ 至于进阶教程,在写基础入门的过程中就曾经简单的构思过,用百度脑图列了下大纲: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c497749.jpg) 当时想着写完入门休息一个月,然后就开始写进阶系列的,大概一个月一个专题这样。 不过,进阶部分可能不会继续写,可能你会不解,为什么不写呢?坦白说下自己的一些难处吧: **首先是**:花在写教程上的时间,一篇简单的教程至少需要花费我2个多小时的时间,尽管内容 比较简单,而复杂一点的,我可能需要花上2,3天!写教程不同于写笔记,要描述清晰, 写例子,贴运行效果等,笔记自己看懂就好,而教程你要让别人也看懂... **接着是**:自己的进步缓慢,写完这套基本教程,和写之前的我相比,并没有什么进步; 依旧还是以前的水平...每次去面试来来去去都是说那几个破旧的项目,一点意思都没有, 我想花点时间做点什么~想学的东西有太多太多,比如,从5月份我就开始接触rxjava,然后 现在烂大街了,我还只会最简单的玩法~ **最后是**:写教程不会给我带来任何的收入,上面也说了,我是一个3K实习狗,而写这套 教程是没有任何收入的,而且每个月偶尔还要给几块钱给七牛,因为图都是用的七牛的 图床,万恶的爬虫网站,把我的文章都爬过去了,然后还不注明出处,然后拼命下我的图... 这是10月份到11月份的下载流量! ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c4b5885.jpg) 我不是富二代,记得之前也说过,我爸得了抑郁症,没工作能力的事,我妈在老家陪我爸, 也就是没收入来源,所幸的是我爸没事熬过来了,而且我已经不用每年再去支付2W的学费; 尽管每月3k的工资可以维持我的生活,但是作为家里的长子,总得扛起家里的大梁吧! 毕竟还有在读大学的弟弟和妹妹,假如我能有毕业证,现在的情况可能好一点吧! 算了,过去的事就过去了,更重要的是以后!我也想每天闲着研究新东西,然后写写 教程啊,但是理想总是很美好的,但现实往往很残酷,我也要生活。 另外说到博客专家这个衔头,很多朋友喜欢拿我这个来黑我,其实并没有什么大用, 每个月原创超过10篇就能收到一本书而已,大部分是C币商城里的一些旧书... **5**.**小猪接下来的想做点什么?** 答:来一次说走就走的旅行,嗨一个月,然后等过年! 好吧,我也想,可惜兜里没钱,接下来的日子嘛,想把公司的项目琢磨透,修下bug,然后学一些 其他的东西,接着写点小玩意玩玩,存钱买个机械键盘(ikbc G104),复习下高数准备一月份 补考等等,然后过完年,可能跑深圳那边找找机会吧~可能偶尔会更新那么一两篇文章吧, 不过不要期望太大,进阶系列也不是说不写,只是暂时不会写,等找到一份稳定的工作,有了 一定经济能力,再开始写吧~ * * * ## 致谢: 嗯,好吧,唠唠叨叨地终于把自己肚子里的东西都吐出来了~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c4c45a7.jpg) 按照一般的套路,肯定要说一堆,谢谢ccav之类的吧,嗯! 谢谢w3c鸟巢的站长FK对每篇文章的认真排版,以及小猪秘密基地里的基神,B神,街神,曹神等的 技术支持,还有一直默默支持小猪的各位朋友,在这里真挚的说一声感谢~ 好了,就说这么多吧,谨以此文纪念我将近两年的csdn博客生涯吧~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c4e7285.jpg)**完结散花**~ **是终点也是起点** to be continued... 待续
';

第十一章——由来、答疑和资源

最后更新于:2022-04-01 05:30:30

';

10.14 Android GPS初涉

最后更新于:2022-04-01 05:30:28

## 本节引言: 说到GPS这个名词,相信大家都不陌生,GPS全球定位技术嘛,嗯,Android中定位的方式 一般有这四种:GPS定位,WIFI定准,基站定位,AGPS定位(基站+GPS); 本系列教程只讲解GPS定位的基本使用!GPS是通过与卫星交互来获取设备当前的经纬度,准确 度较高,但也有一些缺点,最大的缺点就是:**室内几乎无法使用**...需要收到4颗卫星或以上 信号才能保证GPS的准确定位!但是假如你是在室外,无网络的情况,GPS还是可以用的! 本节我们就来探讨下Android中的GPS的基本用法~ * * * ## 1.定位相关的一些API * * * ### **1)LocationManager** 官方API文档:[LocationManager](http://androiddoc.qiniudn.com/reference/android/location/LocationManager.html) 这玩意是系统服务来的,不能直接new,需要: ~~~ LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE); ~~~ 另外用GPS定位别忘了加权限: ~~~ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> ~~~ 好的,获得了LocationManager对象后,我们可以调用下面这些常用的方法: * **addGpsStatusListener**(GpsStatus.Listener listener):添加一个GPS状态监听器 * **addProximityAlert**(double latitude, double longitude, float radius, long expiration, PendingIntent intent): 添加一个临界警告 * **getAllProviders**():获取所有的LocationProvider列表 * **getBestProvider**(Criteria criteria, boolean enabledOnly):根据指定条件返回最优LocationProvider * **getGpsStatus**(GpsStatus status):获取GPS状态 * **getLastKnownLocation**(String provider):根据LocationProvider获得最近一次已知的Location * **getProvider**(String name):根据名称来获得LocationProvider * **getProviders**(boolean enabledOnly):获取所有可用的LocationProvider * **getProviders**(Criteria criteria, boolean enabledOnly):根据指定条件获取满足条件的所有LocationProvider * **isProviderEnabled**(String provider):判断指定名称的LocationProvider是否可用 * **removeGpsStatusListener**(GpsStatus.Listener listener):删除GPS状态监听器 * **removeProximityAlert**(PendingIntent intent):删除一个临近警告 * **requestLocationUpdates**(long minTime, float minDistance, Criteria criteria, PendingIntent intent): 通过制定的LocationProvider周期性地获取定位信息,并通过Intent启动相应的组件 * **requestLocationUpdates**(String provider, long minTime, float minDistance, LocationListener listener): 通过制定的LocationProvider周期性地获取定位信息,并触发listener所对应的触发器 * * * ### **2)LocationProvider(定位提供者)** 官方API文档:[LocationProvider](http://androiddoc.qiniudn.com/reference/android/location/LocationProvider.html) 这比是GPS定位组件的抽象表示,调用下述方法可以获取该定位组件的相关信息! 常用的方法如下: * **getAccuracy**():返回LocationProvider精度 * **getName**():返回LocationProvider名称 * **getPowerRequirement**():获取LocationProvider的电源需求 * **hasMonetaryCost**():返回该LocationProvider是收费还是免费的 * **meetsCriteria**(Criteria criteria):判断LocationProvider是否满足Criteria条件 * **requiresCell**():判断LocationProvider是否需要访问网络基站 * **requiresNetwork**():判断LocationProvider是否需要访问网络数据 * **requiresSatellite**():判断LocationProvider是否需要访问基于卫星的定位系统 * **supportsAltitude**():判断LocationProvider是否支持高度信息 * **supportsBearing**():判断LocationProvider是否支持方向信息 * **supportsSpeed**():判断是LocationProvider否支持速度信息 * * * ### **3)Location(位置信息)** 官方API文档:[Location](http://androiddoc.qiniudn.com/reference/android/location/Location.html) 位置信息的抽象类,我们可以调用下述方法获取相关的定位信息! 常用方法如下: * float **getAccuracy**():获得定位信息的精度 * double **getAltitude**():获得定位信息的高度 * float **getBearing**():获得定位信息的方向 * double **getLatitude**():获得定位信息的纬度 * double **getLongitude**():获得定位信息的精度 * String **getProvider**():获得提供该定位信息的LocationProvider * float **getSpeed**():获得定位信息的速度 * boolean **hasAccuracy**():判断该定位信息是否含有精度信息 * * * ### **4)Criteria(过滤条件)** 官方API文档:[Criteria](http://androiddoc.qiniudn.com/reference/android/location/Criteria.html) 获取LocationProvider时,可以设置过滤条件,就是通过这个类来设置相关条件的~ 常用方法如下: * **setAccuracy**(int accuracy):设置对的精度要求 * **setAltitudeRequired**(boolean altitudeRequired):设置是否要求LocationProvider能提供高度的信息 * **setBearingRequired**(boolean bearingRequired):设置是否要LocationProvider求能提供方向信息 * **setCostAllowed**(boolean costAllowed):设置是否要求LocationProvider能提供方向信息 * **setPowerRequirement**(int level):设置要求LocationProvider的耗电量 * **setSpeedRequired**(boolean speedRequired):设置是否要求LocationProvider能提供速度信息 * * * ## 2.获取LocationProvider的例子 **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c23d4b4.jpg) 由图可以看到,当前可用的LocationProvider有三个,分别是: * **passive**:被动提供,由其他程序提供 * **gps**:通过GPS获取定位信息 * **network**:通过网络获取定位信息 **实现代码**: 布局文件:**activity_main.xml**: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="获得系统所有的LocationProvider" /> <Button android:id="@+id/btn_two" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="根据条件获取LocationProvider" /> <Button android:id="@+id/btn_three" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="获取指定的LocationProvider" /> <TextView android:id="@+id/tv_result" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:background="#81BB4D" android:padding="5dp" android:textColor="#FFFFFF" android:textSize="20sp" android:textStyle="bold" /> </LinearLayout> ~~~ MainActivity.java: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_one; private Button btn_two; private Button btn_three; private TextView tv_result; private LocationManager lm; private List<String> pNames = new ArrayList<String>(); // 存放LocationProvider名称的集合 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE); bindViews(); } private void bindViews() { btn_one = (Button) findViewById(R.id.btn_one); btn_two = (Button) findViewById(R.id.btn_two); btn_three = (Button) findViewById(R.id.btn_three); tv_result = (TextView) findViewById(R.id.tv_result); btn_one.setOnClickListener(this); btn_two.setOnClickListener(this); btn_three.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_one: pNames.clear(); pNames = lm.getAllProviders(); tv_result.setText(getProvider()); break; case R.id.btn_two: pNames.clear(); Criteria criteria = new Criteria(); criteria.setCostAllowed(false); //免费 criteria.setAltitudeRequired(true); //能够提供高度信息 criteria.setBearingRequired(true); //能够提供方向信息 pNames = lm.getProviders(criteria, true); tv_result.setText(getProvider()); break; case R.id.btn_three: pNames.clear(); pNames.add(lm.getProvider(LocationManager.GPS_PROVIDER).getName()); //指定名称 tv_result.setText(getProvider()); break; } } //遍历数组返回字符串的方法 private String getProvider(){ StringBuilder sb = new StringBuilder(); for (String s : pNames) { sb.append(s + "\n"); } return sb.toString(); } } ~~~ * * * ## 3.判断GPS是否打开以及打开GPS的两种方式 在我们使用GPS定位前的第一件事应该是去判断GPS是否已经打开或可用,没打开的话我们需要去 打开GPS才能完成定位!这里不考虑AGPS的情况~ * * * ### **1)判断GPS是否可用** ~~~ private boolean isGpsAble(LocationManager lm){ return lm.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER)?true:false; } ~~~ * * * ### **2)检测到GPS未打开,打开GPS** **方法一**:强制打开GPS,Android 5.0后无用.... ~~~ //强制帮用户打开GPS 5.0以前可用 private void openGPS(Context context){ Intent gpsIntent = new Intent(); gpsIntent.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); gpsIntent.addCategory("android.intent.category.ALTERNATIVE"); gpsIntent.setData(Uri.parse("custom:3")); try { PendingIntent.getBroadcast(LocationActivity.this, 0, gpsIntent, 0).send(); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } } ~~~ **方法二**:打开GPS位置信息设置页面,让用户自行打开 ~~~ //打开位置信息设置页面让用户自己设置 private void openGPS2(){ Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivityForResult(intent,0); } ~~~ ## 4.动态获取位置信息 这个非常简单,调用requestLocationUpdates方法设置一个LocationListener定时检测位置而已! 示例代码如下: 布局:**activity_location.xml**: ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_show" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="5dp" android:textSize="20sp" android:textStyle="bold" /> </LinearLayout> ~~~ LocationActivity.java: ~~~ /** * Created by Jay on 2015/11/20 0020. */ public class LocationActivity extends AppCompatActivity { private LocationManager lm; private TextView tv_show; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_location); tv_show = (TextView) findViewById(R.id.tv_show); lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (!isGpsAble(lm)) { Toast.makeText(LocationActivity.this, "请打开GPS~", Toast.LENGTH_SHORT).show(); openGPS2(); } //从GPS获取最近的定位信息 Location lc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); updateShow(lc); //设置间隔两秒获得一次GPS定位信息 lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 8, new LocationListener() { @Override public void onLocationChanged(Location location) { // 当GPS定位信息发生改变时,更新定位 updateShow(location); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { // 当GPS LocationProvider可用时,更新定位 updateShow(lm.getLastKnownLocation(provider)); } @Override public void onProviderDisabled(String provider) { updateShow(null); } }); } //定义一个更新显示的方法 private void updateShow(Location location) { if (location != null) { StringBuilder sb = new StringBuilder(); sb.append("当前的位置信息:\n"); sb.append("精度:" + location.getLongitude() + "\n"); sb.append("纬度:" + location.getLatitude() + "\n"); sb.append("高度:" + location.getAltitude() + "\n"); sb.append("速度:" + location.getSpeed() + "\n"); sb.append("方向:" + location.getBearing() + "\n"); sb.append("定位精度:" + location.getAccuracy() + "\n"); tv_show.setText(sb.toString()); } else tv_show.setText(""); } private boolean isGpsAble(LocationManager lm) { return lm.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER) ? true : false; } //打开设置页面让用户自己设置 private void openGPS2() { Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivityForResult(intent, 0); } } ~~~ 好的,非常简单,因为gps需要在室外才能用,于是趁着这个机会小跑出去便利店买了杯奶茶, 顺道截下图~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c25a092.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c26cd4f.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c27c23b.jpg) **requestLocationUpdates** (String provider, long minTime, float minDistance, LocationListener listener) 当时间超过minTime(单位:毫秒),或者位置移动超过minDistance(单位:米),就会调用listener中的方法更新GPS信息,建议这个minTime不小于60000,即1分钟,这样会更加高效而且省电,加入你需要尽可能 实时地更新GPS,可以将minTime和minDistance设置为0 对了,别忘了,你还需要一枚权限: ~~~ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> ~~~ * * * ## 5.临近警告(地理围栏) 嗯,就是固定一个点,当手机与该点的距离少于指定范围时,可以触发对应的处理! 有点像地理围栏...我们可以调用LocationManager的addProximityAlert方法添加临近警告! 完整方法如下: **addProximityAlert**(double latitude,double longitude,float radius,long expiration,PendingIntent intent) 属性说明: * **latitude**:指定固定点的经度 * **longitude**:指定固定点的纬度 * **radius**:指定半径长度 * **expiration**:指定经过多少毫秒后该临近警告就会过期失效,-1表示永不过期 * **intent**:该参数指定临近该固定点时触发该intent对应的组件 **示例代码如下**: **ProximityActivity.java**: ~~~ /** * Created by Jay on 2015/11/21 0021. */ public class ProximityActivity extends AppCompatActivity { private LocationManager lm; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_proximity); lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE); //定义固定点的经纬度 double longitude = 113.56843; double latitude = 22.374937; float radius = 10; //定义半径,米 Intent intent = new Intent(this, ProximityReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(this, -1, intent, 0); lm.addProximityAlert(latitude, longitude, radius, -1, pi); } } ~~~ 还需要注册一个广播接收者:**ProximityReceiver.java**: ~~~ /** * Created by Jay on 2015/11/21 0021. */ public class ProximityReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { boolean isEnter = intent.getBooleanExtra( LocationManager.KEY_PROXIMITY_ENTERING, false); if(isEnter) Toast.makeText(context, "你已到达南软B1栋附近", Toast.LENGTH_LONG).show(); else Toast.makeText(context, "你已离开南软B1栋附近", Toast.LENGTH_LONG).show(); } } ~~~ 别忘了注册: ~~~ <receiver android:name=".ProximityReceiver"/> ~~~ **运行效果图**: PS:好吧,设置了10m,结果我从B1走到D1那边,不止10m了吧...还刚好下雨![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c28c7db.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c29ba76.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c2ab3bd.jpg) * * * ## 6.本节示例代码下载 [GPSDemo.zip](http://static.runoob.com/download/GPSDemo.zip) * * * ## 本节小结: > 好的,本节给大家介绍了Android中GPS定位的一些基本用法,非常简单,内容部分参考的 李刚老师的《Android疯狂讲义》,只是对例子进行了一些修改以及进行了可用性的测试! 本节就到这里,谢谢~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c2bb241.jpg)
';

10.12 传感器专题(4)——其他传感器了解

最后更新于:2022-04-01 05:30:26

## 本节引言: 在上一节的结尾说了,传感器部分因为笔者没怎么玩过,本节就简单的把剩下的几个常用的 传感器介绍一遍,当作科普,以后用到再慢慢研究~ ## 1.磁场传感器(Magnetic field sensor) **作用**:该传感器主要用于读取手机附近的磁场变化 **传感器的值采集到的值**:有三个,分别是:X,Y,Z轴上方向上的磁场值 **数值单位**:T,微特斯拉 **传感器获取**:Sensor.**TYPE_MAGNETIC_FIELD** * * * ## 2.距离传感器(Proximity sensor) **作用**:用于感应手机与人体的距离,用得最多的就是手机通话时候,脸部贴近屏幕时, 屏幕会熄灭,当脸部离开屏幕一段距离后,屏幕又会亮起,这样可以避免通过过程脸部误碰 挂断按钮,从而导致通话中断~我们可以利用这个传感器来做一些交互型的App~ **传感器的值采集到的值**:有一个,物体与设备间的距离 **数值单位**:cm,厘米 **传感器获取**:Sensor.**TYPE_PROXIMITY** **其他**: * ①关于距离传感器可能有两种,一种是能直接给出距离的,而另一种则是给出靠近或者远离! 就是只返回两个值,0.0或者最大值!我们可以通过对比解析度和最大值是否相等进行判断! 假如相等说明是后者,假如不等说明是前者! * ②调用sensor.getResolution()方法获得解析度,调用getMaximumRange()获得最大值! * * * ## 3.光线传感器(Light sensor) **作用**:用来读取光度值,即光线强度 **传感器的值采集到的值**:有一个,光亮度值 **数值单位**:lux,1流明每平方米面积,就是1勒克斯(lux),最大值是:120000.0f,Android 中把光线强度分了不同的等级,可以自行查看SensorManager类~ **传感器获取**:Sensor.**TYPE_LIGHT** * * * ## 4.气压传感器(Pressure sensor) **作用**:用于测量大气压力,常用于测量海拔高度 **传感器的值采集到的值**:有一个,大气压值 **数值单位**:hPa,百帕 **传感器获取**:Sensor.**TYPE_PRESSURE** * * * ## 5.温度传感器(Temperature sensor) **作用**:测量手机内部的温度或者外部环境的问题 **传感器的值采集到的值**:有一个,温度值 **数值单位**:℃,摄氏度 **传感器获取**:Sensor.**TYPE_TEMPERATURE**(手机内部)/**TYPE_AMBIENT_TEMPERATURE**(手机外部) * * * ## 6.传感器模拟工具——SensorSimulator 如题,当我们的真机不具备某种传感器的时候,而又需要进行开发~关于具体用法可见下面的文章: [Android设备上的传感器模拟工具:SensorSimulator](http://www.cnblogs.com/mengdd/archive/2013/05/18/3085703.html) * * * ## 本节小结: > 好的,本节应该是基础入门系列里最鸡肋的一节了吧,本来不想写的,不过还是写下吧, 上面的东西知道下就好~还是那句话,以后要用到再深入研究~谢谢 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c080de7.jpg)
';

10.12 传感器专题(3)——加速度/陀螺仪传感器

最后更新于:2022-04-01 05:30:23

## 本节引言: 本节继续来扣Android中的传感器,本节带来的是加速度传感器(Accelerometer sensor)以及 陀螺仪传感器(Gyroscope sensor),和上一节的方向传感器一样有着x,y,z 三个轴, 还是要说一点:x,y轴的坐标要和绘图那里的x,y轴区分开来!传感器的是以左下角 为原点的!x向右,y向上!好的,带着我们的套路来学本节的传感器吧! 另外,想说一点的就是我们不是专门搞这个的,就写东西啊玩玩,见识下而已哈,很多东西 别太较真!![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bfb14b7.jpg) **PS**:方向传感器其实就是利用加速度传感器和磁场传感器来获取方位的,在2.2开始就被弃用了~ * * * ## 1.加速度传感器(Accelerometer sensor) ### **1)名词概念:** > * 加速度传感器的**单位**:**加速度(m/s^2)** > * 方向传感器获取到的加速度是:**手机运动的加速度与重力加速度(9.81m/s^2)的合加速度** > * 另外重力加速度是**垂直向下**的! 关于这个不同方向合加速度的计算好像蛮复杂的,这里我们就不去纠结这个了! 先来看看加速度的value数组中的三个数的值吧~依旧是上节的代码,改下传感器而已~ **水平放置**:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bfc1bf8.jpg) **竖直平放**:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bfd08b4.jpg) **竖直横放**:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bfe5318.jpg) 从上面我们知道value数组的三个值分别对应X,Y,Z轴上的加速度! 好的,知道个大概,我们来写个简易计步器来熟悉下用法! ### **2).简易计步器的实现** **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c0003bd.jpg) **代码实现**: 布局代码:**activity_main.xml**: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="30dp" android:text="简易计步器" android:textSize="25sp" /> <TextView android:id="@+id/tv_step" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="5dp" android:text="0" android:textColor="#DE5347" android:textSize="100sp" android:textStyle="bold" /> <Button android:id="@+id/btn_start" android:layout_width="match_parent" android:layout_height="64dp" android:text="开始" android:textSize="25sp" /> </LinearLayout> ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener, SensorEventListener { private SensorManager sManager; private Sensor mSensorAccelerometer; private TextView tv_step; private Button btn_start; private int step = 0; //步数 private double oriValue = 0; //原始值 private double lstValue = 0; //上次的值 private double curValue = 0; //当前值 private boolean motiveState = true; //是否处于运动状态 private boolean processState = false; //标记当前是否已经在计步 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sManager = (SensorManager) getSystemService(SENSOR_SERVICE); mSensorAccelerometer = sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sManager.registerListener(this, mSensorAccelerometer, SensorManager.SENSOR_DELAY_UI); bindViews(); } private void bindViews() { tv_step = (TextView) findViewById(R.id.tv_step); btn_start = (Button) findViewById(R.id.btn_start); btn_start.setOnClickListener(this); } @Override public void onSensorChanged(SensorEvent event) { double range = 1; //设定一个精度范围 float[] value = event.values; curValue = magnitude(value[0], value[1], value[2]); //计算当前的模 //向上加速的状态 if (motiveState == true) { if (curValue >= lstValue) lstValue = curValue; else { //检测到一次峰值 if (Math.abs(curValue - lstValue) > range) { oriValue = curValue; motiveState = false; } } } //向下加速的状态 if (motiveState == false) { if (curValue <= lstValue) lstValue = curValue; else { if (Math.abs(curValue - lstValue) > range) { //检测到一次峰值 oriValue = curValue; if (processState == true) { step++; //步数 + 1 if (processState == true) { tv_step.setText(step + ""); //读数更新 } } motiveState = true; } } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} @Override public void onClick(View v) { step = 0; tv_step.setText("0"); if (processState == true) { btn_start.setText("开始"); processState = false; } else { btn_start.setText("停止"); processState = true; } } //向量求模 public double magnitude(float x, float y, float z) { double magnitude = 0; magnitude = Math.sqrt(x * x + y * y + z * z); return magnitude; } @Override protected void onDestroy() { super.onDestroy(); sManager.unregisterListener(this); } } ~~~ 好的,真的是非常简易的计步器...上面的步数是我坐着拿手撸出来的... ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c019a10.jpg),毕竟写来玩玩而已~ * * * ## 2.陀螺仪传感器(Gyroscope sensor) ### **1)名词概念:** 陀螺仪又叫角速度传感器,一般用来检测手机姿态的,好像手机中的陀螺仪传感器一般都是三轴的! 体感游戏用得最多,手机拍照防抖,GPS惯性导航,还有为APP添加一些动作感应(比如轻轻晃动手机 关闭来电铃声)等等,具体的可以自己去百度下~ * 陀螺仪传感器的**单位**:**角速度(弧度/秒)radians/second** * 获得传感器用的是:**Sensor.TYPE_GYROSCOPE** 他的三个值依次是沿着X轴,Y轴,Z轴旋转的角速度,手机逆时针旋转,角速度值为正,顺时针则为负值! 经常用于计算手机已经转动的角度!这是网上的一段代码~ ~~~ private static final float NS2S = 1.0f / 1000000000.0f; private float timestamp; public void onSensorChanged(SensorEvent event) { if (timestamp != 0) { // event.timesamp表示当前的时间,单位是纳秒(1百万分之一毫秒) final float dT = (event.timestamp - timestamp) * NS2S; angle[0] += event.values[0] * dT; angle[1] += event.values[1] * dT; angle[2] += event.values[2] * dT; } timestamp = event.timestamp; } ~~~ 通过陀螺仪传感器相邻两次获得数据的时间差(dT)来分别计算在这段时间内手机延X、 Y、Z轴旋转的角度,并将值分别累加到angle数组的不同元素上 * * * ## 3.本节示例代码下载: [SensorDemo4.zip](http://static.runoob.com/download/SensorDemo4.zip) * * * ## 本节小结: > 好的,本节给大家简单的跟大家介绍了下加速度传感器和陀螺仪,写了个简易计步器, 感觉传感器没怎么玩过,没什么好写,算了,下节就简单的把剩下的传感器介绍下 算了,就当科普科普,以后要用到再深入研究吧~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79c028d5a.jpg)
';

10.11 传感器专题(2)——方向传感器

最后更新于:2022-04-01 05:30:21

## 本节引言: 在上一节中我们中我们对传感器的一些基本概念进行了学习,以及学习了使用传感器的套路, 本节给大家带来的传感器是方向传感器的用法,好的,开始本节内容~ * * * ## 1.三维坐标系的概念: 在Android平台中,传感器框架通常是使用一个标准的三维坐标系来表示一个值的。以本节 要讲的方向传感器为例子,确定一个方向也需要一个三维坐标,毕竟我们的设备不可能永远 都是水平端着的吧,安卓给我们返回的方向值就是一个长度为3的flaot数组,包含三个方向 的值!官方API文档中有这样一个图:[sensors_overview](http://androiddoc.qiniudn.com/guide/topics/sensors/sensors_overview.html) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79beae80f.jpg) 如果你看不懂图,那么写下文字解释: * **X轴的方向**:沿着屏幕水平方向从左到右,如果手机如果不是是正方形的话,较短的边需要水平 放置,较长的边需要垂直放置。 * **Y轴的方向**:从屏幕的左下角开始沿着屏幕的的垂直方向指向屏幕的顶端 * **Z轴的方向**:当水平放置时,指向天空的方向 * * * ## 2.方向传感器的三个值 上一节中说了,传感器的回调方法:onSensorChanged中的参数SensorEvent event,event的 值类型是Float[]的,而且最多只有三个元素,而方向传感器则刚好有三个元素,都表示度数! 对应的含义如下: **values[0]:**方位角,手机绕着Z轴旋转的角度。0表示正北(North),90表示正东(East), 180表示正南(South),270表示正西(West)。假如values[0]的值刚好是这四个值的话, 并且手机沿水平放置的话,那么当前手机的正前方就是这四个方向,可以利用这一点来 写一个指南针! **values[1**]:倾斜角,手机翘起来的程度,当手机绕着x轴倾斜时该值会发生变化。取值 范围是[-180,180]之间。假如把手机放在桌面上,而桌面是完全水平的话,values[1](http://androiddoc.qiniudn.com/guide/topics/sensors/sensors_overview.html)的则应该 是0,当然很少桌子是绝对水平的。从手机**顶部开始抬起**,直到手机沿着x轴旋转180(此时屏幕 乡下水平放在桌面上)。在这个旋转过程中,values[**1**]的值会从**0到-180**之间变化,即手机抬起 时,values[1](http://androiddoc.qiniudn.com/guide/topics/sensors/sensors_overview.html)的值会逐渐变小,知道等于-180;而加入从手机**底部开始抬起**,直到手机沿着x轴 旋转180度,此时values[**1**]的值会**从0到180**之间变化。我们可以利用value[**1**]的这个特性结合 value[**2**]来实现一个平地尺! **value[2**]:滚动角,沿着Y轴的滚动角度,取值范围为:[-90,90],假设将手机屏幕朝上水平放在 桌面上,这时如果桌面是平的,values[2](http://www.runoob.com/wp-content/uploads/2015/11/18139494.jpg)的值应为0。将手机从左侧逐渐抬起,values[**2**]的值将 逐渐减小,知道垂直于手机放置,此时values[**2**]的值为-90,从右侧则是0-90;加入在垂直位置 时继续向右或者向左滚动,values[**2**]的值将会继续在-90到90之间变化! 假如你不是很懂,没事我们写个demo验证下就知道了~ * * * ## 3.简单的Demo帮助我们理解这三个值的变化: **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bec5a44.jpg) **实现代码**: 布局代码:**activity_main.xml**: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp"> <TextView android:id="@+id/tv_value1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="方位角" android:textSize="18sp" android:textStyle="bold" /> <TextView android:id="@+id/tv_value2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="倾斜角" android:textSize="18sp" android:textStyle="bold" /> <TextView android:id="@+id/tv_value3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="滚动角" android:textSize="18sp" android:textStyle="bold" /> </LinearLayout> ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements SensorEventListener { private TextView tv_value1; private TextView tv_value2; private TextView tv_value3; private SensorManager sManager; private Sensor mSensorOrientation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sManager = (SensorManager) getSystemService(SENSOR_SERVICE); mSensorOrientation = sManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); sManager.registerListener(this, mSensorOrientation, SensorManager.SENSOR_DELAY_UI); bindViews(); } private void bindViews() { tv_value1 = (TextView) findViewById(R.id.tv_value1); tv_value2 = (TextView) findViewById(R.id.tv_value2); tv_value3 = (TextView) findViewById(R.id.tv_value3); } @Override public void onSensorChanged(SensorEvent event) { tv_value1.setText("方位角:" + (float) (Math.round(event.values[0] * 100)) / 100); tv_value2.setText("倾斜角:" + (float) (Math.round(event.values[1] * 100)) / 100); tv_value3.setText("滚动角:" + (float) (Math.round(event.values[2] * 100)) / 100); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } } ~~~ 代码非常简单~,你想真正体验下这三个值的变化,自己运行下程序转转手机就知道了~ * * * ## 4.一个简易版的文字指南针示例 下面我们来写个简单的文字版的指南针来体验体验,当文字显示正南的时候,表示手机 的正前方就是南方! **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bee8aba.jpg) **代码实现**: 自定义View:**CompassView.java** ~~~ /** * Created by Jay on 2015/11/14 0014. */ public class CompassView extends View implements Runnable{ private Paint mTextPaint; private int sWidth,sHeight; private float dec = 0.0f; private String msg = "正北 0°"; public CompassView(Context context) { this(context, null); } public CompassView(Context context, AttributeSet attrs) { super(context, attrs); sWidth = ScreenUtil.getScreenW(context); sHeight = ScreenUtil.getScreenH(context); init(); new Thread(this).start(); } public CompassView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private void init() { mTextPaint = new Paint(); mTextPaint.setColor(Color.GRAY); mTextPaint.setTextSize(64); mTextPaint.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText(msg, sWidth / 4 , sWidth / 2, mTextPaint); } // 更新指南针角度 public void setDegree(float degree) { // 设置灵敏度 if(Math.abs(dec - degree) >= 2 ) { dec = degree; int range = 22; String degreeStr = String.valueOf(dec); // 指向正北 if(dec > 360 - range && dec < 360 + range) { msg = "正北 " + degreeStr + "°"; } // 指向正东 if(dec > 90 - range && dec < 90 + range) { msg = "正东 " + degreeStr + "°"; } // 指向正南 if(dec > 180 - range && dec < 180 + range) { msg = "正南 " + degreeStr + "°"; } // 指向正西 if(dec > 270 - range && dec < 270 + range) { msg = "正西 " + degreeStr + "°"; } // 指向东北 if(dec > 45 - range && dec < 45 + range) { msg = "东北 " + degreeStr + "°"; } // 指向东南 if(dec > 135 - range && dec < 135 + range) { msg = "东南 " + degreeStr + "°"; } // 指向西南 if(dec > 225 - range && dec < 225 + range) { msg = "西南 " + degreeStr + "°"; } // 指向西北 if(dec > 315 - range && dec < 315 + range) { msg = "西北 " + degreeStr + "°"; } } } @Override public void run() { while(!Thread.currentThread().isInterrupted()) { try { Thread.sleep(100); } catch(InterruptedException e) { Thread.currentThread().interrupt(); } postInvalidate(); } } } ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements SensorEventListener { private CompassView cView; private SensorManager sManager; private Sensor mSensorOrientation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); cView = new CompassView(MainActivity.this); sManager = (SensorManager) getSystemService(SENSOR_SERVICE); mSensorOrientation = sManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); sManager.registerListener(this, mSensorOrientation, SensorManager.SENSOR_DELAY_UI); setContentView(cView); } @Override public void onSensorChanged(SensorEvent event) { cView.setDegree(event.values[0]); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override protected void onDestroy() { super.onDestroy(); sManager.unregisterListener(this); } } ~~~ 这就是一个很简单的指南针的雏形了,有兴趣的可以自己绘制个罗盘和指针,然后实现一个 好看的指南针~ * * * ## 5.本节示例代码下载: [SensorDemo2.zip](http://static.runoob.com/download/SensorDemo2.zip) [SensorDemo3.zip](http://static.runoob.com/download/SensorDemo3.zip) * * * ## 本节小结: > 好的,本节给大家介绍了Android中最常用的方向传感器,以及他的简单用法,以及 写了一个指南针的例子,而完成指南针我们只用到一个values[0]的值,利用其他两个 值我们还可以用来测量某地是否平躺,即制作水平尺,有空的可以写个来玩玩~ 好的,就到这里,谢谢~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ae1ebc7.jpg)
';

10.10 传感器专题(1)——相关介绍

最后更新于:2022-04-01 05:30:19

## 1.传感器相关介绍: 说到传感器,相信大家都不会陌生吧,比如微信的摇一摇就用到了加速度传感器; **传感器的定义**:一种物理设备或者生物器官,能够探测、感受外界的信号,物理条件(如光,热, 适度)或化学组成(如烟雾),并将探知的信息传递给其他的设备或者器官! **传感器的种类**:可以从不同的角度对传感器进行划分,转换原理(传感器工作的基本物理或化学 效应);用途;输出信号以及制作材料和工艺等。一般是按工作原来来分:物理传感器与化学传感器 两类!手机上搭载的基本都是物理传感器,手机上搭载的传感器有下面这些: * **方向传感器(Orientation sensor)** * **加速感应器(Accelerometer sensor)** * **陀螺仪传感器(Gyroscope sensor)** * **磁场传感器(Magnetic field sensor)** * **距离传感器(Proximity sensor)** * **光线传感器(Light sensor)** * **气压传感器(Pressure sensor)** * **温度传感器(Temperature sensor)** * **重力感应器(Gravity sensor,Android 2.3引入)** * **线性加速感应器(Linear acceleration sensor ,Android 2.3引入)** * **旋转矢量传感器(Rotation vector sensor,Android 2.3)** * **相对湿度传感器(Relative humidity sensor,Android 4.0)** * **近场通信(NFC)传感器(Android 2.3引入),NFC和其他不一样,具有读写功能。** 当然除了这些以外还有其他比如心率传感器,记步传感器,指纹传感器等, 关于Android设备所支持的传感器类型可见官方文档:[Sensor](http://androiddoc.qiniudn.com/reference/android/hardware/Sensor.html) Summary部分的内容~ * * * ## 2.如何查看自己手机所支持的传感器有哪些? 上面说的这么多种肯定不是所有手机都具备的,每台手机上搭载的传感器类型 以及数目都可能是不一样的,比如我手头上的Nexus 5支持的传感器类型有: 重力,光线,距离,气压和陀螺仪!而令意外moto x 二代则有重力,光线, 距离和红外传感器!关于自己手机支持的传感器类型,你可以到相关的评测 网站比如:中关村手机在线,太平洋等,搜索到自己的机型查看相关参数! 当然,我们也可以自己写代码来看看自己手机支持的传感器类型~ **代码示例**: **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bd717a6.jpg) **代码实现**: **activity_main.xml**: ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/txt_show" android:layout_width="match_parent" android:layout_height="match_parent" /> </ScrollView> </RelativeLayout> ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity { private TextView txt_show; private SensorManager sm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sm = (SensorManager) getSystemService(Context.SENSOR_SERVICE); txt_show = (TextView) findViewById(R.id.txt_show); List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL); StringBuilder sb = new StringBuilder(); sb.append("此手机有" + allSensors.size() + "个传感器,分别有:\n\n"); for(Sensor s:allSensors){ switch (s.getType()){ case Sensor.TYPE_ACCELEROMETER: sb.append(s.getType() + " 加速度传感器(Accelerometer sensor)" + "\n"); break; case Sensor.TYPE_GYROSCOPE: sb.append(s.getType() + " 陀螺仪传感器(Gyroscope sensor)" + "\n"); break; case Sensor.TYPE_LIGHT: sb.append(s.getType() + " 光线传感器(Light sensor)" + "\n"); break; case Sensor.TYPE_MAGNETIC_FIELD: sb.append(s.getType() + " 磁场传感器(Magnetic field sensor)" + "\n"); break; case Sensor.TYPE_ORIENTATION: sb.append(s.getType() + " 方向传感器(Orientation sensor)" + "\n"); break; case Sensor.TYPE_PRESSURE: sb.append(s.getType() + " 气压传感器(Pressure sensor)" + "\n"); break; case Sensor.TYPE_PROXIMITY: sb.append(s.getType() + " 距离传感器(Proximity sensor)" + "\n"); break; case Sensor.TYPE_TEMPERATURE: sb.append(s.getType() + " 温度传感器(Temperature sensor)" + "\n"); break; default: sb.append(s.getType() + " 其他传感器" + "\n"); break; } sb.append("设备名称:" + s.getName() + "\n 设备版本:" + s.getVersion() + "\n 供应商:" + s.getVendor() + "\n\n"); } txt_show.setText(sb.toString()); } } ~~~ * * * ## 3.Sensor传感器相关的方法以及使用套路 从2中的例子我们可以大概地总结下获取Sensor传感器以及获取传感器相关的一些信息 流程如下: ### 1)Sensor传感器的相关方法 **Step 1:获得传感器管理器**: ~~~ SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE); ~~~ * **Step 2:获得设备的传感器对象的列表**: ~~~ List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL); ~~~ * **Step 3**:迭代获取Sensor对象,然后调用对应方法获得传感器的相关信息: ~~~ for(Sensor s:allSensors){ sensor.getName(); //获得传感器名称 sensor.getType(); //获得传感器种类 sensor.getVendor(); //获得传感器供应商 sensor.getVersion(); //获得传感器版本 sensor.getResolution(); //获得精度值 sensor.getMaximumRange(); //获得最大范围 sensor.getPower(); //传感器使用时的耗电量 } ~~~ * * * ### 2)传感器的使用套路 一般我们是很少说直接去获取Sensor,然后获取上面这些信息的!因为这没什么 大的作用,我们更多的时候是去获取传感器采集到的数据,比如获取当前的大气压, 或者方向传感器三个角的值,或者陀螺仪的值这样~而大部分的传感器数据采集都是 下面的一个套路: **~Step 1:获得传感器管理器:** ~~~ SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE); ~~~ **~Step 2:调用特定方法获得需要的传感器:** 比如这里获取的是方向传感器,想获得什么传感器自己查API~: ~~~ Sensor mSensorOrientation = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION); ~~~ **~Step 3:实现SensorEventListener接口,重写onSensorChanged和onAccuracyChanged的方法!** **onSensorChanged**:当传感器的值变化时会回调 **onAccuracyChanged**:当传感器的进度发生改变时会回调 ~~~ @Override public void onSensorChanged(SensorEvent event) { final float[] _Data = event.values; this.mService.onSensorChanged(_Data[0],_Data[1],_Data[2]); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } ~~~ 我们一般获取传感器数据的来源就是这个SensorEvent,这个类中有一个**values的变量**, 类型是**Float[]**,该变量**最多有只有三个元素**,而且传感器不同,对应元素代表的含义也不同, 比如方向传感器中第一个元素是方位角的值,而气压传感器中第一个值则是气压值! **~Step 4:SensorManager对象调用registerListener注册监听器:** ~~~ <code>ms.registerListener(mContext, mSensorOrientation, android.hardware.SensorManager.SENSOR_DELAY_UI); </code> ~~~ 方法也很简单,对应的参数:**上下文对象**,**Sensor传感器对象**, 以及**传感器的延时时间的精度密度**,有四个可选值: * **SENSOR_DELAY_FASTEST**——延时:**0ms** * **SENSOR_DELAY_GAME**——延时:**20ms** * **SENSOR_DELAY_UI**——延时:**60ms** * **SENSOR_DELAY_NORMAL**——延时:**200ms** 当然低延时意味着更频繁的检车,更意味着更多的电量消耗,如果不是要求精度非常高的建议 别使用太高精度的,一般用第三个较多~自己衡量衡量吧~ **~Step 5:监听器的取消注册:** 用完就放,一个很好的习惯,一般我们可以把他写到Activity或者Service的销毁方法中: ~~~ ms.registerListener(mContext, mSensorOrientation, android.hardware.SensorManager.SENSOR_DELAY_UI); ~~~ 好的,套路非常简单~ * * * ## 4.本节示例代码下载: [SensorDemo1.zip](http://static.runoob.com/download/SensorDemo1.zip) * * * ## 本节小结: > 好的,本节给大家讲解了下Android中的传感器的介绍以及如何了解自己手机所支持的传感器, 除了网上查,也可以自己写代码测,然后还讲解了Sensor传感器相关信息获取的方法流程,最后 还讲解了采集传感器数据的套路,后面我们会针对一些常用的传感器的用法进行剖析,敬请期待~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ae1ebc7.jpg)
';

10.9 WallpaperManager(壁纸管理器)

最后更新于:2022-04-01 05:30:16

## 本节引言: 本节给大家带来的是WallpaperManager(壁纸管理器),如其名,就是手机壁纸相关的 一个API,在本节中我们会描述下WallpaperManager的基本用法,调用系统自带的 壁纸选择功能,将Activity的背景设置为壁纸背景,以及写一个定时换壁纸的例子~ 好了,不BB,开始本节内容~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bab766c.jpg) **官方API文档**:[WallpaperManager](http://androiddoc.qiniudn.com/reference/android/app/WallpaperManager.html) * * * ## 1.WallpaperManager的基本用法 ### 相关方法 设置壁纸的相关方法: * **setBitmap**(Bitmap bitmap):将壁纸设置为bitmap所代表的位图 * **setResource**(int resid):将壁纸设置为resid资源所代表的图片 * **setStream**(InputStream data):将壁纸设置为data数据所代表的图片 其他方法: * **clear**():清除壁纸,设置回系统默认的壁纸 * **getDesiredMinimumHeight**():最小壁纸高度 * **getDesiredMinimumWidth**():最小壁纸宽度 * **getDrawable**():获得当前系统壁纸,如果没有设置壁纸,则返回系统默认壁纸 * **getWallpaperInfo**():加入当前壁纸是动态壁纸,返回动态壁纸信息 * **peekDrawable**():获得当前系统壁纸,如果没设置壁纸的话返回null ### 获得WallpaperManager对象 ~~~ WallpaperManager wpManager =WallpaperManager.getInstance(this); ~~~ ### 设置壁纸需要的权限 ~~~ <uses-permission android:name="android.permission.SET_WALLPAPER"/> ~~~ * * * ## 2.调用系统自带的壁纸选择功能 ~~~ Button btn_set = (Button) findViewById(R.id.btn_set); btn_set.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent chooseIntent = new Intent(Intent.ACTION_SET_WALLPAPER); startActivity(Intent.createChooser(chooseIntent, "选择壁纸")); } }); ~~~ **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bad4cbf.jpg) * * * ## 3.将Activity的背景设置为壁纸背景 方法有两种,一种是在Activity中用代码进行设置,另一种是在AndroidManifest.xml中修改 Activity的主题~! ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bb677a7.jpg) **方法一:Activity中设置**: ~~~ @Override protected void onCreate(Bundle savedInstanceState) { setTheme(android.R.style.Theme_Wallpaper_NoTitleBar_Fullscreen); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } ~~~ **方法二:AndroidManifest.xml修改theme**: ~~~ <activity android:name=".MainActivity" android:theme="@android:style/Theme.Wallpaper.NoTitleBar"/> ~~~ * * * ## 4.定时换壁纸的Demo 这里用到前面学的AlarmManager(闹钟服务),假如你对它不了解的话可以到: [10.5 AlarmManager(闹钟服务)](http://www.runoob.com/w3cnote/android-tutorial-alarmmanager.html "10.5 AlarmManager(闹钟服务)")进行学习~ 下面我们来写个Demo~ **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bb924ca.jpg) **代码实现**: 首先我们来写一个定时换壁纸的Service:**WallPaperService.java** ~~~ /** * Created by Jay on 2015/11/13 0013. */ public class WallPaperService extends Service { private int current = 0; //当前壁纸下标 private int[] papers = new int[]{R.mipmap.gui_1,R.mipmap.gui_2,R.mipmap.gui_3,R.mipmap.gui_4}; private WallpaperManager wManager = null; //定义WallpaperManager服务 @Override public void onCreate() { super.onCreate(); wManager = WallpaperManager.getInstance(this); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if(current >= 4)current = 0; try{ wManager.setResource(papers[current++]); }catch(Exception e){e.printStackTrace();} return START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } } ~~~ 接着撸个简单的布局,三个Button:**activity_main.xml**: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_on" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开启自动换壁纸" /> <Button android:id="@+id/btn_off" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="关闭自动换壁纸" /> <Button android:id="@+id/btn_clean" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="清除壁纸" /> </LinearLayout> ~~~ 接着是我们的Activity,在这里实例化aManager并设置定时事件~:**MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_on; private Button btn_off; private Button btn_clean; private AlarmManager aManager; private PendingIntent pi; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //①获得AlarmManager对象: aManager = (AlarmManager) getSystemService(ALARM_SERVICE); //②指定要启动的Service,并指明动作是Servce: Intent intent = new Intent(MainActivity.this, WallPaperService.class); pi = PendingIntent.getService(MainActivity.this, 0, intent, 0); bindViews(); } private void bindViews() { btn_on = (Button) findViewById(R.id.btn_on); btn_off = (Button) findViewById(R.id.btn_off); btn_clean = (Button) findViewById(R.id.btn_clean); btn_on.setOnClickListener(this); btn_off.setOnClickListener(this); btn_clean.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_on: aManager.setRepeating(AlarmManager.RTC_WAKEUP, 0, 3000, pi); btn_on.setEnabled(false); btn_off.setEnabled(true); Toast.makeText(MainActivity.this, "自动更换壁纸设置成功", Toast.LENGTH_SHORT).show(); break; case R.id.btn_off: btn_on.setEnabled(true); btn_off.setEnabled(false); aManager.cancel(pi); break; case R.id.btn_clean: try { WallpaperManager.getInstance(getApplicationContext()).clear(); Toast.makeText(MainActivity.this, "清除壁纸成功~", Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); } break; } } } ~~~ 最后别忘了加上设置壁纸的权限以及为我们的Service进行注册:**AndroidManifest.xml**: ~~~ <uses-permission android:name="android.permission.SET_WALLPAPER" /> <service android:name=".WallPaperService"/> ~~~ 好的,非常简单~ * * * ## 5.本节示例代码下载 [WallpaperManagerDemo.zip](http://static.runoob.com/download/WallpaperManagerDemo.zip) * * * ## 本节小结: > 好的,本节给大家介绍了下WallpaperManager的一些基本用法~更多的东西还需你们自己 进行探究~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79bc09aff.jpg)谢谢~!
';

10.8 LayoutInflater(布局服务)

最后更新于:2022-04-01 05:30:14

## 本节引言: >  本节继续带来的是Android系统服务中的LayoutInflater(布局服务),说到布局,大家第一时间 可能想起的是写完一个布局的xml,然后调用Activity的setContentView()加载布局,然后把他显示 到屏幕上是吧~其实这个底层走的还是这个LayoutInflater,用的Android内置的Pull解析器来解析 布局。一般在Android动态加载布局或者添加控件用得较多,本节我们就来学习下他在实际开发中 的一些用法~ > > **官方API文档**:[LayoutInflater](http://androiddoc.qiniudn.com/reference/android/view/LayoutInflater.html) * * * ## 1.LayoutInflater的相关介绍 * * * ### 1)Layout是什么鬼? > 答:一个用于加载布局的系统服务,就是实例化与Layout XML文件对应的View对象,不能直接使用, 需要通过**getLayoutInflater**( )方法或**getSystemService**( )方法来获得与当前Context绑定的 LayoutInflater实例! * * * ### 2)LayoutInflater的用法 **①获取LayoutInflater实例的三种方法**: ~~~ LayoutInflater inflater1 = LayoutInflater.from(this); LayoutInflater inflater2 = getLayoutInflater(); LayoutInflater inflater3 = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); ~~~ PS:后面两个其实底层走的都是第一种方法~ **②加载布局的方法**: > public View **inflate** (int resource, ViewGroup root, boolean attachToRoot) 该方法的三个参数依次为: > > ①要加载的布局对应的资源id > > ②为该布局的外部再嵌套一层父布局,如果不需要的话,写null就可以了! > > ③是否为加载的布局文件的最外层套一层root布局,不设置该参数的话, 如果root不为null的话,则默认为true 如果root为null的话,attachToRoot就没有作用了! root不为null,attachToRoot为true的话,会在加载的布局文件最外层嵌套一层root布局; 为false的话,则root失去作用! 简单理解就是:**是否为加载的布局添加一个root的外层容器~!** * * * **③通过LayoutInflater.LayoutParams来设置相关的属性**: > 比如RelativeLayout还可以通过addRule方法添加规则,就是设置位置:是参考父容器呢? 还是参考子控件?又或者设置margin等等,这个由你决定~ * * * ## 2.纯Java代码加载布局 > 我们早已习惯了使用XML生成我们需要的布局,但是在一些特定的情况下,我们 需要使用Java代码往我们的布局中动态的添加组件或者布局! > > 但是不建议大家完全地使用Java代码来编写Android页面布局,首先一点就是代码会多, 一多久容易乱,而且不利于业务的分离,我们还是建议使用xml来完成布局,然后通过 Java代码对里面的组件进行修改,当然有些时候可能需要使用Java动态的来添加组件! ### **纯Java代码加载布局的流程**: * * * **——Step 1**: ①**创建容器**:LinearLayout ly = new LinearLayout(this); ②**创建组件**:Button btnOne = new Button(this); **——Step 2:** 可以为容器或者组件设置相关属性: 比如:**LinearLayout**,我们可以设置组件的排列方向:**ly.setOrientation(LinearLayout.VERTICAL);** 而组件也可以:比如Button:**btnOne.setText("按钮1");** 关于设置属性的方法可参见Android 的API,通常xml设置的属性只需在前面添加:set即可,比如 **setPadding**(左,上,右,下); **——Step 3:** 将组件或容器添加到容器中,这个时候我们可能需要设置下组件的添加位置,或者设置他的大小: 我们需要用到一个类:LayoutParams,我们可以把它看成布局容器的一个信息包!封装位置与大小 等信息的一个类!先演示下设置大小的方法:(前面的LinearLayout可以根据不同容器进行更改) ~~~ LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); ~~~ 很简单,接着就到这个设置位置了,设置位置的话,通常我们考虑的只是RelativeLayout! 这个时候用到LayoutParams的addRule( )方法!可以添加多个addRule( )哦! 设置组件在父容器中的位置, 比如**设置组件的对其方式**: ~~~ RelativeLayout rly = new RelativeLayout(this); RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); Button btnOne = new Button(this); rly.addView(btnOne, lp2); ~~~ **参照其他组件的对其方式**: (有个缺点,就是要为参考组件手动设置一个id,是**手动**!!!!) 比如:设置btnOne居中后,让BtnTwo位于btnOne的下方以及父容器的右边! ~~~ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); RelativeLayout rly = new RelativeLayout(this); Button btnOne = new Button(this); btnOne.setText("按钮1"); Button btnTwo = new Button(this); btnTwo.setText("按钮2"); // 为按钮1设置一个id值 btnOne.setId(123); // 设置按钮1的位置,在父容器中居中 RelativeLayout.LayoutParams rlp1 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); rlp1.addRule(RelativeLayout.CENTER_IN_PARENT); // 设置按钮2的位置,在按钮1的下方,并且对齐父容器右面 RelativeLayout.LayoutParams rlp2 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); rlp2.addRule(RelativeLayout.BELOW, 123); rlp2.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); // 将组件添加到外部容器中 rly.addView(btnTwo, rlp2); rly.addView(btnOne, rlp1); // 设置当前视图加载的View即rly setContentView(rly); } } ~~~ **——step 4:** 调用setContentView( )方法加载布局对象即可! 另外,如果你想移除某个容器中的View,可以调用容器.**removeView**(要移除的组件); **运行截图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b9d892f.jpg) * * * ## 3.Java代码动态添加控件或xml布局 > 第二点我们讲解了使用纯Java代码来加载布局,实际当中用得并不多,更多的时候是动态 的添加View控件以及动态的加载XML布局! ### **1)Java代码动态增加View** 动态添加组件的写法有两种,区别在于是否需要先**setContentView(R.layout.activity_main)**; 下面演示下两种不同写法添加一个Button的例子: 先写个布局文件先:**activity_main.xml**: ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/txtTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="我是xml文件加载的布局"/> </RelativeLayout> ~~~ **第一种不需要setContentView()加载布局文件先:** ~~~ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button btnOne = new Button(this); btnOne.setText("我是动态添加的按钮"); RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp2.addRule(RelativeLayout.CENTER_IN_PARENT); LayoutInflater inflater = LayoutInflater.from(this); RelativeLayout rly = (RelativeLayout) inflater.inflate( R.layout.activity_main, null) .findViewById(R.id.RelativeLayout1); rly.addView(btnOne,lp2); setContentView(rly); } } ~~~ **第二种不需要setContentView()加载布局文件先:** ~~~ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btnOne = new Button(this); btnOne.setText("我是动态添加的按钮"); RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp2.addRule(RelativeLayout.CENTER_IN_PARENT); RelativeLayout rly = (RelativeLayout) findViewById(R.id.RelativeLayout1); rly.addView(btnOne,lp2); } } ~~~ **分析总结**: > 代码很简单,创建按钮后,我们又创建了一个LayoutParams对象,用来设置Button的大小, 又通过addRule()方法设置了Button的位置! > > **第一种方法**:通过LayoutInflate的inflate()方法加载了activity_main布局,获得了外层容器, 接着addView添加按钮进容器,最后setContentView(); > > **第二种方法**:因为我们已经通过setContetView()方法加载了布局,此时我们就可以通过 findViewById找到这个外层容器,接着addView,最后setContentView()即可! > > 另外,关于这个setContentView( )他设置的视图节点是整个XML的根节点! * * * ### 2)Java代码动态加载xml布局 接下来的话,我们换一个,这次加载的是xml文件!**动态地添加xml文件**! 先写下主布局文件和动态加载的布局文件: **activity_main.xml**: ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/btnLoad" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="动态加载布局"/> </RelativeLayout> ~~~ **inflate.xml**: ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" android:id="@+id/ly_inflate" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我是Java代码加载的布局" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我是布局里的一个小按钮" /> </LinearLayout> ~~~ 接着到我们的**MainActivity.java**在这里动态加载xml布局: ~~~ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获得LayoutInflater对象; final LayoutInflater inflater = LayoutInflater.from(this); //获得外部容器对象 final RelativeLayout rly = (RelativeLayout) findViewById(R.id.RelativeLayout1); Button btnLoad = (Button) findViewById(R.id.btnLoad); btnLoad.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //加载要添加的布局对象 LinearLayout ly = (LinearLayout) inflater.inflate( R.layout.inflate_ly, null, false).findViewById( R.id.ly_inflate); //设置加载布局的大小与位置 RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.addRule(RelativeLayout.CENTER_IN_PARENT); rly.addView(ly,lp); } }); } } ~~~ **运行截图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b9e7d97.jpg) **代码分析**: > **①获取容器对象**: > > ~~~ > final RelativeLayout rly = (RelativeLayout) findViewById(R.id.RelativeLayout1); > ~~~ > > **②获得Inflater对象,同时加载被添加的布局的xml,通过findViewById找到最外层的根节点** > > ~~~ > final LayoutInflater inflater = LayoutInflater.from(this); > LinearLayout ly = (LinearLayout) inflater.inflate(R.layout.inflate_ly, null, false) > .findViewById(R.id.ly_inflate); > ~~~ > > **③为这个容器设置大小与位置信息:** > > ~~~ > RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( > LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); > lp.addRule(RelativeLayout.CENTER_IN_PARENT); > ~~~ > > **④添加到外层容器中:** > > ~~~ > rly.addView(ly,lp); > ~~~ * * * ## 4.LayoutInflater的inflate()方法源码 > 最后提供下LayoutInflater的inflate()方法的源码吧,有兴趣的可以看看~,其实就是Pull解析而已~ ~~~ public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); mConstructorArgs[0] = mContext; View result = root; try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("merge can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, attrs); } else { View temp = createViewFromTag(name, attrs); ViewGroup.LayoutParams params = null; if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { temp.setLayoutParams(params); } } rInflate(parser, temp, attrs); if (root != null && attachToRoot) { root.addView(temp, params); } if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } return result; } } ~~~ * * * ## 本节小结: > 本节给大家讲解了一下Android中的LayoutInflater(布局服务),以及动态加载View和控件 相关的东西,相信对初学控件的朋友带来帮助~好的,就说这么多,谢谢~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ba07f3f.jpg)
';

10.7 WindowManager(窗口管理服务)

最后更新于:2022-04-01 05:30:12

## 本节引言: > 本节给大家带来的Android给我们提供的系统服务中的——WindowManager(窗口管理服务), 它是显示View的最底层,Toast,Activity,Dialog的底层都用到了这个WindowManager, 他是全局的!该类的核心无非:调用addView,removeView,updateViewLayout这几个方法 来显示View以及通过WindowManager.LayoutParams这个API来设置相关的属性! > > 本节我们就来探讨下这个WindowManager在实际开发中的一些应用实例吧~ > > 官方API文档:[WindowManager](http://androiddoc.qiniudn.com/reference/android/view/WindowManager.html) * * * ## 1.WindowManager的一些概念: ### 1)WindowManager介绍 > Android为我们提供的用于与窗口管理器进行交互的一个API!我们都知道App的界面都是 由一个个的Acitivty组成,而Activity又由View组成,当我们想显示一个界面的时候, 第一时间想起的是:Activity,对吧?又或者是Dialog和Toast。 > > 但是有些情况下,前面这三者可能满足不了我们的需求,比如我们仅仅是一个简单的显示 用Activity显得有点多余了,而Dialog又需要Context对象,Toast又不可以点击... 对于以上的情况我们可以利用WindowManager这个东东添加View到屏幕上, 或者从屏幕上移除View!他就是管理Android窗口机制的一个接口,显示View的最底层! * * * ### 2)如何获得WindowManager实例 ①**获得WindowManager对象**: ~~~ WindowManager wManager = getApplicationContext().getSystemService(Context. WINDOW_ SERVICE); ~~~ ②**获得WindowManager.LayoutParams对象,为后续操作做准备** ~~~ WindowManager.LayoutParams wmParams=new WindowManager.LayoutParams(); ~~~ * * * ## 2.WindowManager使用实例: ### **实例1:获取屏幕宽高** 在Android 4.2前我们可以用下述方法获得屏幕宽高: ~~~ public static int[] getScreenHW(Context context) { WindowManager manager = (WindowManager)context .getSystemService(Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); int[] HW = new int[] { width, height }; return HW; } ~~~ 而上述的方法在Android 4.2以后就过时了,我们可以用另一种方法获得屏幕宽高: ~~~ public static int[] getScreenHW2(Context context) { WindowManager manager = (WindowManager) context. getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(dm); int width = dm.widthPixels; int height = dm.heightPixels; int[] HW = new int[] { width, height }; return HW; } ~~~ 然后我们可以再另外写两个获取宽以及高的方法,这里以第二种获得屏幕宽高为例: ~~~ public static int getScreenW(Context context) { return getScreenHW2(context)[0]; } public static int getScreenH(Context context) { return getScreenHW2(context)[1]; } ~~~ 当然,假如你不另外写一个工具类的话,你可以直接直接获取,比如: ~~~ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WindowManager wManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wManager.getDefaultDisplay().getMetrics(dm); Toast.makeText(MainActivity.this, "当前手机的屏幕宽高:" + dm.widthPixels + "*" + dm.heightPixels, Toast.LENGTH_SHORT).show(); } } ~~~ **运行结果**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b764c9f.jpg) * * * ### **实例2:设置窗口全屏显示** ~~~ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); getSupportActionBar().hide(); ~~~ **运行结果**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b775329.jpg) * * * ### **实例3:保持屏幕常亮** ~~~ public void setKeepScreenOn(Activity activity,boolean keepScreenOn) { if(keepScreenOn) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); }else{ activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } ~~~ * * * ### **实例4:简单悬浮框的实现** **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b78dd70.jpg) **实现代码**: 首先我们需要一个后台的Service在后台等待我们的操作,比如完成悬浮框的绘制移除等, 于是乎我们定义一个Service:**MyService.java**: 我们需要一个创建悬浮框View的一个方法: ~~~ private void createWindowView() { btnView = new Button(getApplicationContext()); btnView.setBackgroundResource(R.mipmap.ic_launcher); windowManager = (WindowManager) getApplicationContext() .getSystemService(Context.WINDOW_SERVICE); params = new WindowManager.LayoutParams(); // 设置Window Type params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; // 设置悬浮框不可触摸 params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应 params.format = PixelFormat.RGBA_8888; // 设置悬浮框的宽高 params.width = 200; params.height = 200; params.gravity = Gravity.LEFT; params.x = 200; params.y = 000; // 设置悬浮框的Touch监听 btnView.setOnTouchListener(new View.OnTouchListener() { //保存悬浮框最后位置的变量 int lastX, lastY; int paramX, paramY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); paramX = params.x; paramY = params.y; break; case MotionEvent.ACTION_MOVE: int dx = (int) event.getRawX() - lastX; int dy = (int) event.getRawY() - lastY; params.x = paramX + dx; params.y = paramY + dy; // 更新悬浮窗位置 windowManager.updateViewLayout(btnView, params); break; } return true; } }); windowManager.addView(btnView, params); isAdded = true; } ~~~ 然后我们只需在OnCreate( )方法中调用上述的createWindowView( )方法即可启动加载悬浮框, 但是我们发现了一点:这玩意貌似关不掉啊,卧槽,好吧,接下来我们就要分析下需求了! 当处于手机的普通界面,即桌面的时候,这玩意才显示,而当我们启动其他App时,这个悬浮框应该 消失不见,当我们推出app又回到桌面,这个悬浮框又要重新出现! 那么我们首先需要判断App是否位于桌面,于是乎我们再加上下述代码: ~~~ /** * 判断当前界面是否是桌面 */ public boolean isHome(){ if(mActivityManager == null) { mActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); } List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1); return homeList.contains(rti.get(0).topActivity.getPackageName()); } /** * 获得属于桌面的应用的应用包名称 * @return 返回包含所有包名的字符串列表 */ private List<String> getHomes() { List<String> names = new ArrayList<String>(); PackageManager packageManager = this.getPackageManager(); // 属性 Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for(ResolveInfo ri : resolveInfo) { names.add(ri.activityInfo.packageName); } return names; } ~~~ 好了,接下来我们需要每隔一段时间来进行一系列的判断,比如:是否在桌面,是否已加载悬浮框, 否则加载;否则,如果加载了,就将这个悬浮框移除!这里我们使用handler~,因为不能在子线程直接 更新UI,所以,你懂的,所以我们自己写一个handler来完成上述的操作: ~~~ //定义一个更新界面的Handler private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case HANDLE_CHECK_ACTIVITY: if(isHome()) { if(!isAdded) { windowManager.addView(btnView, params); isAdded = true; new Thread(new Runnable() { public void run() { for(int i=0;i<10;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace();} Message m = new Message(); m.what=2; mHandler.sendMessage(m); } } }).start();} } else { if(isAdded) { windowManager.removeView(btnView); isAdded = false; } } mHandler.sendEmptyMessageDelayed(HANDLE_CHECK_ACTIVITY, 0); break; } } }; ~~~ 最后要做的一件事,就是重写Service的onStartCommand( )方法了,就是做判断,取出Intent中的 数据,判断是需要添加悬浮框,还是要移除悬浮框! ~~~ @Override public int onStartCommand(Intent intent, int flags, int startId) { int operation = intent.getIntExtra(OPERATION, OPERATION_SHOW); switch(operation) { case OPERATION_SHOW: mHandler.removeMessages(HANDLE_CHECK_ACTIVITY); mHandler.sendEmptyMessage(HANDLE_CHECK_ACTIVITY); break; case OPERATION_HIDE: mHandler.removeMessages(HANDLE_CHECK_ACTIVITY); break; } return super.onStartCommand(intent, flags, startId); } ~~~ 好的,至此,主要的工作就完成了,接下来就是一些零碎的东西了,用一个Activity 来启动这个Service:**MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_on; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { btn_on = (Button) findViewById(R.id.btn_on); btn_on.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_on: Intent mIntent = new Intent(MainActivity.this, MainService.class); mIntent.putExtra(MainService.OPERATION, MainService.OPERATION_SHOW); startService(mIntent); Toast.makeText(MainActivity.this, "悬浮框已开启~", Toast.LENGTH_SHORT).show(); break; } } } ~~~ 接着AndroidManifest.xml加上权限,以及为MainService进行注册: ~~~ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.GET_TASKS" /> <service android:name=".MainService"/> ~~~ 好了,逻辑还是比较容易理解的~大家自己再看看吧~ * * * ## 3.文献扩展: > 从第四个实例中,你可能留意到了:WindowManager.LayoutParams这个东东,这是一个标记, 比如全屏~时间关系就不一一列举出来了,可以到官网或者下述链接中查看: > > [官方文档:WindowManager.LayoutParams](http://androiddoc.qiniudn.com/reference/android/view/WindowManager.LayoutParams.html) > > [Android系统服务-WindowManager](http://blog.csdn.net/chenyafei617/article/details/6577940) > > 另外,假如你对上述的悬浮框有兴趣,想更深入的研究,可见郭大叔(郭霖)的博客: > > [Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果](http://blog.csdn.net/guolin_blog/article/details/8689140) > > [Android桌面悬浮窗进阶,QQ手机管家小火箭效果实现](http://blog.csdn.net/guolin_blog/article/details/16919859) * * * ## 4.本节代码示例下载: [WindowManagerDemo2.zip](http://static.runoob.com/download/WindowManagerDemo2.zip) * * * ## 本节小结: > 本节我们对Android系统服务中的WindowManager进行了学习,前面三个实例可能 实际开发中会用得多一点,建议将第一个示例写成一个工具类,毕竟屏幕宽高用得 蛮多的~至于悬浮框那个能看懂就看懂,没看懂耶没什么~实际开发很少叫你弄个 悬浮框吧...嗯,好的,本节就到这里,谢谢~ > > ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b80f4df.jpg)
';

10.6 PowerManager(电源服务)

最后更新于:2022-04-01 05:30:09

## 本节引言: > 本节要讲解的是Android为我们提供的系统服务中的——**PowerManager(电源服务)**,用于 管理CPU运行,键盘或屏幕亮起来;不过,除非迫不得已,否则进来别去使用这个类,假如 你使用以后,一定要及时释放!本节并不会太深入滴去讲解这B,因为这涉及到底层的一些 东西,以后需要用到在深入研究~本节主要介绍的是一些基本的概念,PowerManager,wakelock 锁的机制等! > > 官方API文档:[PowerManager](http://androiddoc.qiniudn.com/reference/android/os/PowerManager.html) * * * ## 1.PowerManager是什么 * * * > Android系统为我们提供的电源管理的一个API,其相关接口与设备电池的续航能力有很大的关联, 官方也说了,除非是迫不得已吧,不然的话,应该尽量避免使用这个类,并且使用完以后一定要及时释放! > > 所谓的电源管理包括:CPU运行,键盘或者屏幕亮起来!核心其实就是**wakelock锁**机制,只要我们拿着这个锁, 那么系统就无法进入休眠状态,可以给用户态程序或内核获取到!锁可以是:"**有超时的**"或者 "**没有超时**",超时的锁到时间后会自动解锁,如果没有了锁或超时,内核会启动休眠机制来进入休眠! * * * ## 2.wakelock锁介绍 * * * > **PowerManager.WakeLock**有**加锁**与**解锁**两种状态,而加锁的形式有两种: > > ①**永久锁住**,这种锁除非显式的放开,否则是不会解锁的,所以用起来需要非常小心! > > ②**超时锁**,到时间后就会解锁,而创建WakeLock后,有**两种加锁机制**: **①不计数锁机制**,**②计数锁机制(默认)**可通过**setReferenceCounted**(boolean value)来指定,区别在于: 前者无论**acquire**( )多少次,一次**release**( )就可以解开锁。 而后者则需要**(--count == 0)**的时候,同样当**(count == 0)**才会去申请锁 所以,**WakeLock**的计数机制并不是正真意义上对每次请求进行申请/释放一个锁; 只是对同一把锁被**申请/释放**的次数来进行统计,然后再去操作! ps:关于更加深入的内容就涉及到底层的内容了,笔者水平有限,还没到那个level, 这里就不深入研究了,就说一些基本的吧,以后有需要的话,再另开一篇吧~ * * * ## 3.PowerManager怎么用 * * * ~~~ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock w1 = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "MyTag"); w1.acquire(); //在这个过程,屏幕会保持光亮! w1.release(); ~~~ 上述**newWakeLock**( )的第一个**flag标记**,这些标记不同程度的影响系统电源. 这些标记都是独占的,并且每次只能指定其中一个。 **PARTIAL_WAKE_LOCK**:保持CPU 运转,屏幕和键盘灯有可能是关闭的。 **SCREEN_DIM_WAKE_LOCK**:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯 **SCREEN_BRIGHT_WAKE_LOCK**:保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯 **FULL_WAKE_LOCK**:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度 > ps:如果你使用的是局部唤醒锁的话(使用**PARTIAL_WAKE_LOCK**标志),CPU会继续运行, 将忽略任何的计时器,甚至按下电源按钮。其他的唤醒锁话,CPU也会继续运转,但是使用者仍 然可以按电源按钮让设备睡眠。另外,你可以使用两个以上的标记,但是他只影响屏幕的行为。 和 **PARTIAL_WAKE_LOCK** 同时使用的话,没有任何影响。 **屏幕解锁参数:** > **ACQUIRE_CAUSES_WAKEUP**:正常唤醒锁实际上并不打开照明。相反,一旦打开他们会一直仍然 保持(例如来世user的activity)。当获得wakelock,这个标志会使屏幕或/和键盘立即打开。 > > 一个典型的使用就是可以立即看到那些对用户重要的通知。 > > **ON_AFTER_RELEASE**:设置了这个标志,当wakelock释放时用户activity计时器会被重置,导致照明 持续一段时间。如果你在wacklock条件中循环,这个可以用来降低闪烁 * * * ## 4.需要的权限 * * * 要进行电源的操作需要在AndroidManifest.xml中声明该应用有设置电源管理的权限: ~~~ <uses-permission android:name="android.permission.WAKE_LOCK"/> ~~~ 你可能还需要: ~~~ <uses-permission android:name="android.permission.DEVICE_POWER"/> ~~~ 另外WakeLock的设置是**Activity级别**的,而不是针对整个Application应用的! * * * ## 本节小结: > 好的,本节介绍了PowerManager(电源服务),不过仅仅是科普一下而已,内容也说了 不到迫不得已尽量别使用这个类~看懂了,或者没看懂都没关系,知道下即可!
';

10.5 AlarmManager(闹钟服务)

最后更新于:2022-04-01 05:30:07

## 本节引言: > 本节带来的Android中的AlarmManager(闹钟服务),听名字我们知道可以通过它开发手机闹钟类的APP, 而在文档中的解释是:在特定的时刻为我们广播一个指定的Intent,简单说就是我们自己定一个时间, 然后当到时间时,AlarmManager会为我们广播一个我们设定好的Intent,比如时间到了,可以指向某个 Activity或者Service!另外官方文档中有一些要注意的地方: > > ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b545d31.jpg) > > 另外要注意一点的是,AlarmManager主要是用来在某个时刻运行你的代码的,即时你的APP在那个特定 时间并没有运行!还有,从API 19开始,Alarm的机制都是非准确传递的,操作系统将会转换闹钟 ,来最小化唤醒和电池的使用!某些新的API会支持严格准确的传递,见 setWindow(int, long, long, PendingIntent)和setExact(int, long, PendingIntent)。 targetSdkVersion在API 19之前应用仍将继续使用以前的行为,所有的闹钟在要求准确传递的情况 下都会准确传递。更多详情可见官方API文档:[AlarmManager](http://androiddoc.qiniudn.com/reference/android/app/AlarmManager.html) * * * ## 1.Timer类与AlarmManager类区别: > 如果你学过J2SE的话,那么你对Timer肯定不会陌生,定时器嘛,一般写定时任务的时候 肯定离不开他,但是在Android里,他却有个短板,不太适合那些需要长时间在后台运行的 定时任务,因为Android设备有自己的休眠策略,当长时间的无操作,设备会自动让CPU进入 休眠状态,这样就可能导致Timer中的定时任务无法正常运行!而AlarmManager则不存在 这种情况,因为他具有唤醒CPU的功能,可以保证每次需要执行特定任务时CPU都能正常工作, 或者说当CPU处于休眠时注册的闹钟会被保留(可以唤醒CPU),但如果设备被关闭,或者重新 启动的话,闹钟将被清除!(Android手机关机闹钟不响...) * * * ## 2.获得AlarmManager实例对象: ~~~ AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); ~~~ * * * ## 3.相关方法讲解: > * **set**(int type,long startTime,PendingIntent pi):一次性闹钟 > * **setRepeating**(int type,long startTime,long intervalTime,PendingIntent pi): 重复性闹钟,和3有区别,3闹钟间隔时间不固定 > * **setInexactRepeating**(int type,long startTime,long intervalTime,PendingIntent pi): 重复性闹钟,时间不固定 > * **cancel**(PendingIntent pi):取消AlarmManager的定时服务 > * **getNextAlarmClock**():得到下一个闹钟,返回值AlarmManager.AlarmClockInfo > * **setAndAllowWhileIdle**(int type, long triggerAtMillis, PendingIntent operation) 和set方法类似,这个闹钟运行在系统处于低电模式时有效 > * **setExact**(int type, long triggerAtMillis, PendingIntent operation): 在规定的时间精确的执行闹钟,比set方法设置的精度更高 > * **setTime**(long millis):设置系统墙上的时间 > * **setTimeZone**(String timeZone):设置系统持续的默认时区 > * **setWindow**(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation): 设置一个闹钟在给定的时间窗触发。类似于set,该方法允许应用程序精确地控制操作系统调 整闹钟触发时间的程度。 **关键参数讲解**: > * **Type**(闹钟类型): 有五个可选值: AlarmManager.**ELAPSED_REALTIME**: 闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3; AlarmManager.**ELAPSED_REALTIME_WAKEUP** 闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2; AlarmManager.**RTC** 闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1; AlarmManager.**RTC_WAKEUP** 表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0; AlarmManager.**POWER_OFF_WAKEUP** 表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持; > * **startTime**:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。 需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对应的闹钟使用的是相对时间 (ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间 (相对于系统启动时间来说),比如当前时间就表示为:SystemClock.elapsedRealtime(); 如果第一个参数对应的闹钟使用的是绝对时间(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP), 那么本属性就得使用绝对时间,比如当前时间就表示 为:System.currentTimeMillis()。 > * **intervalTime**:表示两次闹钟执行的间隔时间,也是以毫秒为单位. > * **PendingIntent**:绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。 PendingIntent是Intent的封装类。需要注意的是,如果是通过启动服务来实现闹钟提 示的话,PendingIntent对象的获取就应该采用Pending.getService (Context c,int i,Intent intent,int j)方法;如果是通过广播来实现闹钟 提示的话,PendingIntent对象的获取就应该采用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;如果是采用Activity的方式来实 现闹钟提示的话,PendingIntent对象的获取就应该采用 PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。 如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。 * * * ## 4.使用示例:一个简单的定时任务 > 要说的是,此例子只在Android 4.4以下的系统可行,5.0以上并不可行,后续如果有5.0 以上AlarmManager的解决方案,到时再补上!另外,这里用set方法可能有点不准,如果要 更精确的话可以使用setExtra()方法来设置AlarmManager! **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b5588ac.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b569b7b.jpg) **实现代码**: 首先一个简单的布局文件:**activity_main.xml**,另外在res创建一个raw文件夹,把音频文件丢进去! 另外创建一个只有外层布局的**activity_clock.xml**作为闹钟响时Activity的布局!没东西,就不贴了 ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_set" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="设置闹钟" /> <Button android:id="@+id/btn_cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="关闭闹钟" android:visibility="gone" /> </LinearLayout> ~~~ 接着是**MainActivity.java**,也很简单: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private Button btn_set; private Button btn_cancel; private AlarmManager alarmManager; private PendingIntent pi; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { btn_set = (Button) findViewById(R.id.btn_set); btn_cancel = (Button) findViewById(R.id.btn_cancel); alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); Intent intent = new Intent(MainActivity.this, ClockActivity.class); pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0); btn_set.setOnClickListener(this); btn_cancel.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_set: Calendar currentTime = Calendar.getInstance(); new TimePickerDialog(MainActivity.this, 0, new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { //设置当前时间 Calendar c = Calendar.getInstance(); c.setTimeInMillis(System.currentTimeMillis()); // 根据用户选择的时间来设置Calendar对象 c.set(Calendar.HOUR, hourOfDay); c.set(Calendar.MINUTE, minute); // ②设置AlarmManager在Calendar对应的时间启动Activity alarmManager.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pi); Log.e("HEHE",c.getTimeInMillis()+""); //这里的时间是一个unix时间戳 // 提示闹钟设置完毕: Toast.makeText(MainActivity.this, "闹钟设置完毕~"+ c.getTimeInMillis(), Toast.LENGTH_SHORT).show(); } }, currentTime.get(Calendar.HOUR_OF_DAY), currentTime .get(Calendar.MINUTE), false).show(); btn_cancel.setVisibility(View.VISIBLE); break; case R.id.btn_cancel: alarmManager.cancel(pi); btn_cancel.setVisibility(View.GONE); Toast.makeText(MainActivity.this, "闹钟已取消", Toast.LENGTH_SHORT) .show(); break; } } } ~~~ 然后是闹铃页面的**ClockActivity.java**: ~~~ /** * Created by Jay on 2015/10/25 0025. */ public class ClockActivity extends AppCompatActivity { private MediaPlayer mediaPlayer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_clock); mediaPlayer = mediaPlayer.create(this,R.raw.pig); mediaPlayer.start(); //创建一个闹钟提醒的对话框,点击确定关闭铃声与页面 new AlertDialog.Builder(ClockActivity.this).setTitle("闹钟").setMessage("小猪小猪快起床~") .setPositiveButton("关闭闹铃", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mediaPlayer.stop(); ClockActivity.this.finish(); } }).show(); } } ~~~ 代码非常简单,核心流程如下: > * AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); **获得系统提供的AlarmManager服务的对象** > * **Intent设置要启动的组件**: Intent intent = new Intent(MainActivity.this, ClockActivity.class); > * **PendingIntent对象设置动作,启动的是Activity还是Service,又或者是广播!** PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0); > * **调用AlarmManager的set( )方法设置单次闹钟的闹钟类型,启动时间以及PendingIntent对象!**alarmManager.set(AlarmManager.RTC_WAKEUP,c.getTimeInMillis(), pi); 另外假如出现闹铃无效的话,你可以从这些方面入手: > 1.系统版本或者手机,5.0以上基本没戏,小米,自行百度吧~ 2.ClockActivity有注册没? 3.假如你用的是alarmManager发送广播,广播再激活Activity的话,则需要为Intent设置一个flag: i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 4. ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b577f4f.jpg)这些地方没写错吧~别把getActivity写成了getService等哦~ 另外,关于AlarmManager结合后来Service实现定时后台任务的例子,可见: [4.2.2 Service进阶](http://www.runoob.com/w3cnote/android-tutorial-service-2.html "4.2.2 Service进阶") * * * ## 5.代码示例下载: [AlarmManagerDemo.zip](http://static.runoob.com/download/AlarmManagerDemo.zip) * * * ## 本节小结: > 好的,本节跟大家讲解了Android中的AlarmManager(闹钟服务)的使用,除了可以像例子那样定制 一个自己的闹钟,也可以结合Service,Thread来完成轮询等,用法多多,还需各位自行探究,嗯 本节就到这里,谢谢~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b586fd4.jpg)
';

10.4 Vibrator(振动器)

最后更新于:2022-04-01 05:30:04

## 本节引言: > 本节我们介绍的是**Vibrator**(振动器),是手机自带的振动器,别去百度直接搜针振动器,因为 你的搜索结果可能是如图所示的神秘的道具,或者其他神秘道具: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b3a6db9.jpg) > > 嗯,说回本节介绍的Vibrator,其实就是Android给我们提供的用于机身震动的一个服务! 比如前面我们的Notification中可以设置震动,当收到推送消息的时候我们可以设置震动 提醒,游戏必备,比如"打飞机"的游戏,当你的飞机给人打爆的时候,会长震动! > > 下面我们就来写个简单的例子,来熟悉下这个Vibrator的用法! > > 官方API文档:[Vibrator](http://androiddoc.qiniudn.com/reference/android/os/Vibrator.html) * * * ## 1.获得Vibrator实例: * * * **Vibrator vb = (Vibrator)getSystemService(Service.VIBRATOR_SERVICE);** * * * ## 2.可以使用的相关方法: > * abstract void **cancel**():关闭或者停止振动器 > * abstract boolean **hasVibrator**():判断硬件是否有振动器 > * void **vibrate**(long milliseconds):控制手机振动为milliseconds毫秒 > * void **vibrate**(long[] pattern,int repeat):指定手机以pattern指定的模式振动! 比如:pattern为new int[200,400,600,800],就是让他在200,400,600,800这个时间交替启动与关闭振动器! 而第二个则是重复次数,如果是-1的只振动一次,如果是0的话则一直振动 还有其他两个方法用得不多~ 对了,使用振动器还需要在AndroidManifest.xml中添加下述权限: * * * ## 3.使用示例:设置频率不同的震动器: > 对于Vibrator用的最广泛的莫过于所谓的手机按摩器类的app,在app市场一搜,一堆,笔者随便下了 几个下来瞅瞅,都是大同小异的,这点小玩意竟然有8W多的下载量...好吧,好像也不算多, 不过普遍功能都是切换振动频率来完成,而所谓的按摩效果,是否真的有效就不得而知了, 那么接下来我们就来实现一个简单的按摩器吧! 核心其实就是:vibrate()中的数组的参数,根据自己需求写一个数组就可以了! 下述代码需要在真机上进行测试! **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b3b903a.jpg) **实现代码**: 简单的布局文件,五个按钮:**activity_main.xml**: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_hasVibrator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="判断是否有振动器" /> <Button android:id="@+id/btn_short" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="短振动" /> <Button android:id="@+id/btn_long" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="长振动" /> <Button android:id="@+id/btn_rhythm" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="节奏振动" /> <Button android:id="@+id/btn_cancle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="取消振动" /> </LinearLayout> ~~~ 接着是**MainActivity.java**部分: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_hasVibrator; private Button btn_short; private Button btn_long; private Button btn_rhythm; private Button btn_cancle; private Vibrator myVibrator; private Context mContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获得系统的Vibrator实例: myVibrator = (Vibrator) getSystemService(Service.VIBRATOR_SERVICE); mContext = MainActivity.this; bindViews(); } private void bindViews() { btn_hasVibrator = (Button) findViewById(R.id.btn_hasVibrator); btn_short = (Button) findViewById(R.id.btn_short); btn_long = (Button) findViewById(R.id.btn_long); btn_rhythm = (Button) findViewById(R.id.btn_rhythm); btn_cancle = (Button) findViewById(R.id.btn_cancle); btn_hasVibrator.setOnClickListener(this); btn_short.setOnClickListener(this); btn_long.setOnClickListener(this); btn_rhythm.setOnClickListener(this); btn_cancle.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_hasVibrator: Toast.makeText(mContext, myVibrator.hasVibrator() ? "当前设备有振动器" : "当前设备无振动器", Toast.LENGTH_SHORT).show(); break; case R.id.btn_short: myVibrator.cancel(); myVibrator.vibrate(new long[]{100, 200, 100, 200}, 0); Toast.makeText(mContext, "短振动", Toast.LENGTH_SHORT).show(); break; case R.id.btn_long: myVibrator.cancel(); myVibrator.vibrate(new long[]{100, 100, 100, 1000}, 0); Toast.makeText(mContext, "长振动", Toast.LENGTH_SHORT).show(); break; case R.id.btn_rhythm: myVibrator.cancel(); myVibrator.vibrate(new long[]{500, 100, 500, 100, 500, 100}, 0); Toast.makeText(mContext, "节奏振动", Toast.LENGTH_SHORT).show(); break; case R.id.btn_cancle: myVibrator.cancel(); Toast.makeText(mContext, "取消振动", Toast.LENGTH_SHORT).show(); } } } ~~~ 对了,别漏了振动器权限哦! ~~~ <uses-permission android:name="android.permission.VIBRATE"/> ~~~ * * * ## 4.示例代码下载: [VibratorDemo.zip](http://static.runoob.com/download/VibratorDemo.zip) * * * ## 本节小结: > 好的,本节我们学习了Vibrator(振动器)的基本使用,代码非常简单,还不赶紧加入到 你的APP中,让你的应用HI起来~,嗯,就说这么多,谢谢,天色不早,小猪还是赶紧回家吧! 毕竟我还是个黄花闺仔!万一湿身了就不好了~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b3e2385.jpg)
';

10.3 AudioManager(音频管理器)

最后更新于:2022-04-01 05:30:02

## 本节引言: > 在多媒体的第一节,我们用SoundPool写了个Duang的示例,小猪点击一个按钮后,突然发出"Duang"的 一声,而且当时的声音很大,吓死宝宝了![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b2bff83.jpg),好在不是上班时间,上班时间偷偷写博客给经理知道 会作死的~嗯,好的,说到这个声音大小就得介绍下Android为我们提供的(音量大小控制)的API: > > **AudioManager(音频管理器)**了,该类位于Android.Media包下,提供了音量控制与铃声模式相关操作! 本节我们就来学下这个东东的用法,你可以写一个Demo,一个简单的静音,每次看小电影之前,先 进Demo点下静音,然后![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b2cd925.jpg),说说而已哈~嗯,话不多说,开始本节内容! > > 官方API文档:[AudioManager](http://androiddoc.qiniudn.com/reference/android/media/AudioManager.html) * * * ## 1.获得AudioManager对象实例 > **AudioManager audiomanage = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);** * * * ## 2.相关方法详解 **常用方法**: > * **adjustVolume**(int direction, int flags): 控制手机音量,调大或者调小一个单位,根据第一个参数进行判断**AudioManager.ADJUST_LOWER**,可调小一个单位; **AudioManager.ADJUST_RAISE**,可调大一个单位 > * **adjustStreamVolume**(int streamType, int direction, int flags): 同上,不过可以选择调节的声音类型 1)streamType参数,指定声音类型,有下述几种声音类型: **STREAM_ALARM**:手机闹铃 **STREAM_MUSIC**:手机音乐  > **STREAM_RING**:电话铃声 **STREAM_SYSTEAM**:手机系统  > **STREAM_DTMF**:音调 **STREAM_NOTIFICATION**:系统提示 > **STREAM_VOICE_CALL**:语音电话 2)第二个参数和上面那个一样,调大或调小音量的 3)可选的标志位,比如AudioManager.**FLAG_SHOW_UI**,显示进度条,AudioManager.**PLAY_SOUND**:播放声音 > * **setStreamVolume**(int streamType, int index, intflags):直接设置音量大小 > * **getMode**( ):返回当前的音频模式 > * **setMode**( ):设置声音模式 有下述几种模式: **MODE_NORMAL**(普通), **MODE_RINGTONE**(铃声), **MODE_IN_CALL**(打电话),**MODE_IN_COMMUNICATION**(通话) > * **getRingerMode**( ):返回当前的铃声模式 > * **setRingerMode**(int streamType):设置铃声模式 有下述几种模式: 如**RINGER_MODE_NORMAL**(普通)、**RINGER_MODE_SILENT**(静音)、**RINGER_MODE_VIBRATE**(震动) > * **getStreamVolume**(int streamType): 获得手机的当前音量,最大值为7,最小值为0,当设置为0的时候,会自动调整为震动模式 > * **getStreamMaxVolume**(int streamType):获得手机某个声音类型的最大音量值 > * **setStreamMute**(int streamType,boolean state):将手机某个声音类型设置为静音 > * **setSpeakerphoneOn**(boolean on):设置是否打开扩音器 > * **setMicrophoneMute**(boolean on):设置是否让麦克风静音 > * **isMicrophoneMute**():判断麦克风是否静音或是否打开 > * **isMusicActive**():判断是否有音乐处于活跃状态 > * **isWiredHeadsetOn**():判断是否插入了耳机 **其他方法**: > * **abandonAudioFocus**(AudioManager.OnAudioFocusChangeListenerl):放弃音频的焦点 > * **adjustSuggestedStreamVolume**(int,int suggestedStreamType intflags): 调整最相关的流的音量,或者给定的回退流 > * **getParameters**(String keys):给音频硬件设置一个varaible数量的参数值 > * **getVibrateSetting**(int vibrateType):返回是否该用户的振动设置为振动类型 > * **isBluetoothA2dpOn**():检查是否A2DP蓝牙耳机音频路由是打开或关闭 > * **isBluetoothScoAvailableOffCall**():显示当前平台是否支持使用SCO的关闭调用用例 > * **isBluetoothScoOn**():检查通信是否使用蓝牙SCO > * **loadSoundEffects**():加载声音效果 > * **playSoundEffect**((int effectType, float volume):播放声音效果 > * **egisterMediaButtonEventReceiver**(ComponentName eventReceiver): 注册一个组件MEDIA_BUTTON意图的唯一接收机 > * **requestAudioFocus**(AudioManager.OnAudioFocusChangeListener l,int streamType,int durationHint) 请求音频的焦点 > * **setBluetoothScoOn**(boolean on):要求使用蓝牙SCO耳机进行通讯 > * **startBluetoothSco/stopBluetoothSco()**():启动/停止蓝牙SCO音频连接 > * **unloadSoundEffects**():卸载音效 * * * ## 3.使用示例 > 嘿嘿,属性蛮多的,有些还涉及到蓝牙这些东东,这里我们只讲解最常见的一些方法! > > 遇到一些特殊的没见过的,我们再来查文档! > > 简单的示例:使用Mediaplayer播放音乐,通过AudioManager调节音量大小与静音! > > 对了,先在res下创建一个raw的文件夹,往里面丢一个MP3资源文件! **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b2dc4f8.jpg) **代码实现**: 布局代码**activity_main.xml**: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="播放" /> <Button android:id="@+id/btn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:enabled="false" android:text="停止" /> <Button android:id="@+id/btn_higher" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="调高音量" /> <Button android:id="@+id/btn_lower" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="调低音量" /> <Button android:id="@+id/btn_quite" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="静音" /> </LinearLayout> ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_start; private Button btn_stop; private Button btn_higher; private Button btn_lower; private Button btn_quite; private MediaPlayer mePlayer; private AudioManager aManager; //定义一个标志用来标示是否点击了静音按钮 private int flag = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获得系统的音频对象 aManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE); //初始化mediaplayer对象,这里播放的是raw文件中的mp3资源 mePlayer = MediaPlayer.create(MainActivity.this, R.raw.countingstars); //设置循环播放: mePlayer.setLooping(true); bindViews(); } private void bindViews() { btn_start = (Button) findViewById(R.id.btn_start); btn_stop = (Button) findViewById(R.id.btn_stop); btn_higher = (Button) findViewById(R.id.btn_higher); btn_lower = (Button) findViewById(R.id.btn_lower); btn_quite = (Button) findViewById(R.id.btn_quite); btn_start.setOnClickListener(this); btn_stop.setOnClickListener(this); btn_higher.setOnClickListener(this); btn_lower.setOnClickListener(this); btn_quite.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_start: btn_stop.setEnabled(true); mePlayer.start(); btn_start.setEnabled(false); break; case R.id.btn_stop: btn_start.setEnabled(true); mePlayer.pause(); btn_stop.setEnabled(false); break; case R.id.btn_higher: // 指定调节音乐的音频,增大音量,而且显示音量图形示意 aManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI); break; case R.id.btn_lower: // 指定调节音乐的音频,降低音量,只有声音,不显示图形条 aManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_PLAY_SOUND); break; case R.id.btn_quite: // 指定调节音乐的音频,根据isChecked确定是否需要静音 flag *= -1; if (flag == -1) { aManager.setStreamMute(AudioManager.STREAM_MUSIC, true); //API 23过期- - // aManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_MUTE, // AudioManager.FLAG_SHOW_UI); //23以后的版本用这个 btn_quite.setText("取消静音"); } else { aManager.setStreamMute(AudioManager.STREAM_MUSIC, false);//API 23过期- - // aManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_UNMUTE, // AudioManager.FLAG_SHOW_UI); //23以后的版本用这个 aManager.setMicrophoneMute(false); btn_quite.setText("静音"); } break; } } } ~~~ 代码还是非常简单的,另外设置静音的方法**setStreamMute**()在API 23版本过期, 可以使用另一个方法adjustStreamVolume(int, int, int),然后第三个属性设置: **ADJUST_MUTE** 或 **ADJUST_UNMUTE**! 对了,还有: 如果**adjustStreamVolume**()的第三个参数你设置了振动(Vibrator), 需要在AndroidManifest.xml中添加这个权限哦! ~~~ <**uses-permission android:name="android.permission.VIBRATE"**/> ~~~ * * * ## 4.代码示例下载 [AudioManagerDemo.zip](http://static.runoob.com/download/AudioManagerDemo.zip) * * * ## 本节小结: > 好的,本节给大家演示了AudioManager用于调节音量的一个简单用法,这个类笔者也不常用 到,以后如果有什么新get的技能再加上吧~嘿嘿,静音Demo写好没?要结合实际需求哈~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b303dd6.jpg) > > 另外,本周博客可能不会更新得太频繁,本周要把公司的WebSocket库替换掉,有得头痛了~ 好的,就说这么多,谢谢~
';

10.2 SmsManager(短信管理器)

最后更新于:2022-04-01 05:30:00

## 本节引言: > 本节带来的是Android中的SmsManager(短息管理器),见名知意,就是用来管理手机短信的, 而该类的应用场景并不多,一般是我们发短信的时候才会用到这个API,当然这种短信是 文字短信,对于彩信过于复杂,而且在QQ微信各种社交APP横行的年代,你会去发1块钱一条的 彩信吗?所以本节我们只讨论发送普通文字短信! 官方文档:[SmsManager](http://androiddoc.qiniudn.com/reference/android/telephony/SmsManager.html) * * * ## 1.调用系统发送短信功能: > 就是把写好的收信人和内容发送到系统的发送短信的界面,用户验证收件人内容是否真正确再点击发送! 说白了就是调用系统发短信的窗口,这样做有一定的好处: > > 这样发短信,app安装的时候就可以**少写一条发短信的权限**,那么诸如360这类安全软件在安装的时候 就不会提醒用户:"这个APP有短信权限,可能会偷偷滴发短信喔",而用户对于偷偷发短信的行为是十分 厌恶的,当然有些人不看直接安装,而有些人可能会觉得会偷偷发短信喔,好恶心的应用,我才不装咧, 又或者直接禁止我们的APP发送短信,那么当我们APP在发送短信的时候就可能会出现一些异常,或者 应用直接崩溃等!所以如果你的应用需要发送短信进行验证或者付费这些东西的话,建议使用这种方式! **核心代码**: ~~~ public void SendSMSTo(String phoneNumber,String message){ //判断输入的phoneNumber是否为合法电话号码 if(PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber)){ //Uri.parse("smsto") 这里是转换为指定Uri,固定写法 Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:"+phoneNumber)); intent.putExtra("sms_body", message); startActivity(intent); } } ~~~ * * * ## 2.调用系统提供的短信接口发送短信 > 这个就需要发短信的权限啦 > > **uses-permission android:name="android.permission.SEND_SMS"**/> > > 我们直接调用SmsManager为我们提供的短信接口发送短信: > > **sendTextMessage**(destinationAddress, scAddress, text, sentIntent, deliverIntent); > > 参数依次是: > > * **destinationAddress**:收信人的电话号码 > * **scAddress**:短信中心的号码,null的话使用当前默认的短信服务中心 > * **text**:短信内容 > * **sentIntent**:短信发送状态的信息:(发送状态的Intent) 如果不为null,当消息成功发送或失败这个PendingIntent就广播。结果代码是Activity.RESULT_OK 表示成功,或RESULT_ERROR_GENERIC_FAILURE、RESULT_ERROR_RADIO_OFF、RESULT_ERROR_NULL_PDU 之一表示错误。对应RESULT_ERROR_GENERIC_FAILURE,sentIntent可能包括额外的"错误代码"包含一 个无线电广播技术特定的值,通常只在修复故障时有用。每一个基于SMS的应用程序控制检测sentIntent。 如果sentIntent是空,调用者将检测所有未知的应用程序,这将导致在检测的时候发送较小数量的SMS。 > * **deliverIntent**:短信是否被对方收到的状态信息:(接收状态的Intent) 如果不为null,当这个短信发送到接收者那里,这个PendtingIntent会被广播, 状态报告生成的pdu(指对等层次之间传递的数据单位)会拓展到数据("pdu") ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b1a2ba1.jpg)...那么复杂,pdu是什么卵?好吧,别纠结,简单知道这些参数是: **电话号码,信息中心,短信内容,是否发送成功的监听,以及收信人是否接受的监听就好了!** **核心代码**: ~~~ public void sendSMS(String phoneNumber,String message){ //获取短信管理器 android.telephony.SmsManager smsManager = android.telephony.SmsManager.getDefault(); //拆分短信内容(手机短信长度限制),貌似长度限制为140个字符,就是 //只能发送70个汉字,多了要拆分成多条短信发送 //第四五个参数,如果没有需要监听发送状态与接收状态的话可以写null List<String> divideContents = smsManager.divideMessage(message); for (String text : divideContents) { smsManager.sendTextMessage(phoneNumber, null, text, sentPI, deliverPI); } } ~~~ 可能你还需要监听短信是否发送成功,或者收信人是否接收到信息,就把下面的加上吧: 1)处理返回发送状态的**sentIntent** ~~~ //处理返回的发送状态 String SENT_SMS_ACTION = "SENT_SMS_ACTION"; Intent sentIntent = new Intent(SENT_SMS_ACTION); PendingIntent sentPI = PendingIntent.getBroadcast(context, 0, sentIntent, 0); //注册发送信息的广播接收者 context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context _context, Intent _intent) { switch (getResultCode()) { case Activity.RESULT_OK: Toast.makeText(context, "短信发送成功", Toast.LENGTH_SHORT).show(); break; case SmsManager.RESULT_ERROR_GENERIC_FAILURE: //普通错误 break; case SmsManager.RESULT_ERROR_RADIO_OFF: //无线广播被明确地关闭 break; case SmsManager.RESULT_ERROR_NULL_PDU: //没有提供pdu break; case SmsManager.RESULT_ERROR_NO_SERVICE: //服务当前不可用 break; } } }, new IntentFilter(SENT_SMS_ACTION)); ~~~ 2)处理返回接收状态的**deliverIntent**: ~~~ //处理返回的接收状态 String DELIVERED_SMS_ACTION = "DELIVERED_SMS_ACTION"; //创建接收返回的接收状态的Intent Intent deliverIntent = new Intent(DELIVERED_SMS_ACTION); PendingIntent deliverPI = PendingIntent.getBroadcast(context, 0,deliverIntent, 0); context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context _context, Intent _intent) { Toast.makeText(context,"收信人已经成功接收", Toast.LENGTH_SHORT).show(); } }, new IntentFilter(DELIVERED_SMS_ACTION)); ~~~ 另外这里涉及到了广播的知识,如果你对广播不怎么了解的话,可以看下: [Android基础入门教程——BroadcastReceiver牛刀小试](http://www.runoob.com/w3cnote/android-tutorial-broadcastreceiver.html "4.3.1 BroadcastReceiver牛刀小试") [Android基础入门教程——4.3.2 BroadcastReceiver庖丁解牛](http://www.runoob.com/w3cnote/android-tutorial-broadcastreceiver-2.html) * * * ## 本节小结: > ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b1b8d84.jpg)好的,本节介绍了SmsManager发送文字短信的两种方式~非常简单~建议还是使用 第一种方案吧,起码用户体验好一点...
';

10.1 TelephonyManager(电话管理器)

最后更新于:2022-04-01 05:29:57

## 本节引言: > 本章节是Android基础入门教程的最后一章,主要讲解是一些零零散散的一些知识点,以及一些遗漏 知识点的补充,这些零散的知识点包括,各种系统服务的使用,比如本节的电话管理器,短信管理器, 振动器,闹钟,壁纸等等,还有传感器之类的东西!乱七八糟什么都有哈!好的,本节我们要学习的 是TelephonyManager,见名知义:用于管理手机通话状态,获取电话信息(设备信息、sim卡信息以及 网络信息),侦听电话状态(呼叫状态服务状态、信号强度状态等)以及可以调用电话拨号器拨打电话! 话不多开始本节内容~ > > 官方API:[TelephonyManager](http://androiddoc.qiniudn.com/reference/android/telephony/TelephonyManager.html) * * * ## 1.获得TelephonyManager的服务对象 **TelephonyManager tManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);** * * * ## 2.用法示例 * * * ### 1)调用拨号器拨打电话号码 ~~~ Uri uri=Uri.parse("tel:"+电话号码); Intent intent=new Intent(Intent.ACTION_DIAL,uri); startActivity(intent); ~~~ * * * ### 2)获取Sim卡信息与网络信息 **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b06bd28.jpg) **实现代码**: 布局文件:**activity_main.xml**: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp" tools:context=".MainActivity"> <TextView android:id="@+id/tv_phone1" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/tv_phone2" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/tv_phone3" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/tv_phone4" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/tv_phone5" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/tv_phone6" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/tv_phone7" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/tv_phone8" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" /> <TextView android:id="@+id/tv_phone9" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" /> </LinearLayout> ~~~ **MainActivity.java:** ~~~ public class MainActivity extends AppCompatActivity { private TextView tv_phone1; private TextView tv_phone2; private TextView tv_phone3; private TextView tv_phone4; private TextView tv_phone5; private TextView tv_phone6; private TextView tv_phone7; private TextView tv_phone8; private TextView tv_phone9; private TelephonyManager tManager; private String[] phoneType = {"未知","2G","3G","4G"}; private String[] simState = {"状态未知","无SIM卡","被PIN加锁","被PUK加锁", "被NetWork PIN加锁","已准备好"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //①获得系统提供的TelphonyManager对象的实例 tManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); bindViews(); } private void bindViews() { tv_phone1 = (TextView) findViewById(R.id.tv_phone1); tv_phone2 = (TextView) findViewById(R.id.tv_phone2); tv_phone3 = (TextView) findViewById(R.id.tv_phone3); tv_phone4 = (TextView) findViewById(R.id.tv_phone4); tv_phone5 = (TextView) findViewById(R.id.tv_phone5); tv_phone6 = (TextView) findViewById(R.id.tv_phone6); tv_phone7 = (TextView) findViewById(R.id.tv_phone7); tv_phone8 = (TextView) findViewById(R.id.tv_phone8); tv_phone9 = (TextView) findViewById(R.id.tv_phone9); tv_phone1.setText("设备编号:" + tManager.getDeviceId()); tv_phone2.setText("软件版本:" + (tManager.getDeviceSoftwareVersion()!= null? tManager.getDeviceSoftwareVersion():"未知")); tv_phone3.setText("运营商代号:" + tManager.getNetworkOperator()); tv_phone4.setText("运营商名称:" + tManager.getNetworkOperatorName()); tv_phone5.setText("网络类型:" + phoneType[tManager.getPhoneType()]); tv_phone6.setText("设备当前位置:" + (tManager.getCellLocation() != null ? tManager .getCellLocation().toString() : "未知位置")); tv_phone7.setText("SIM卡的国别:" + tManager.getSimCountryIso()); tv_phone8.setText("SIM卡序列号:" + tManager.getSimSerialNumber()); tv_phone9.setText("SIM卡状态:" + simState[tManager.getSimState()]); } } ~~~ 对了,别忘了在AndroidManifest.xml中加上权限哦! ~~~ <!-- 添加访问手机位置的权限 --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <!-- 添加访问手机状态的权限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> ~~~ 对了可能你想获取网络制式,而非普通的2G,3G,4G这样,其实我们可以到TelephonyManager类的源码里: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b07b454.jpg) 我们可以根据这个networkType的值,判断不同的网络制式,比如,如果networkType == 1 那个是GPRS这种制式的~而这个networkType的值可以通过 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b0966b4.jpg) 即这个getNetworkType()方法获得!好了,就这么简单,可以像上面列好一个数组然后根据 不同的下标显示不同的值! 对了,还有Sim卡状态的,字符串数组中的值,都可以到源码中看: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b0a8d7f.jpg) 其他的可自行探索~ * * * ### 3)获取手机的信号强度 > 网络信号强度的单位是dBm(毫瓦分贝),一般用负数表示,正常手机信号变化范围是从-110dBm (差)到-50dBm(好)之间,如果你比-50dBm还小的话,说明你就站在基站的附近,比如我的n5显示 的信号强度就是-51dBm,有时是-59dBm,因为隔壁就是南软大楼,上面就有基站... > > 另外2G,3G,4G获得信号强度的方式都是重写PhoneStateListener的onSignalStrengthsChanged() 方法,当信号强度发生改变的时候就会触发这个事件,我们可以在这个事件里获取信号强度! **手机获取信号强度代码示例**: **dBm =-113+2*asu**这是一个固定公式,asu(独立信号单元) **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b0b7e53.jpg) **实现代码**: **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity { private TextView tv_rssi; private MyPhoneStateListener mpsListener; private TelephonyManager tManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tManager = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE)); tv_rssi = (TextView) findViewById(R.id.tv_rssi); mpsListener = new MyPhoneStateListener(); tManager.listen(mpsListener,290); } private class MyPhoneStateListener extends PhoneStateListener { private int asu = 0,lastSignal = 0; @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { asu = signalStrength.getGsmSignalStrength(); lastSignal = -113 + 2 * asu; tv_rssi.setText("当前手机的信号强度:" + lastSignal + " dBm" ); super.onSignalStrengthsChanged(signalStrength); } } } ~~~ 另外因为笔者的卡都是移动卡,联通和电信的不知道,但是从源码里看到这样几个API: * **getEvdoDbm()**:电信3G * **getCdmaDbm()**:联通3G * **getLteDbm()**:4G 这些应该是可以直接获得dBm信号强度的,有条件的可以试试~ 还有,别忘记加上权限了哦! ~~~ <!-- 添加访问手机状态的权限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> ~~~ * * * ### 4)监听手机的所有来电 > 对于监听到的通话记录结果,你可以采取不同的方式获取到,这里用到的是把通话记录写入到文件中, 而你也可以以短信的形式发送给你,或者是上传到某个平台,当然如果通信记录不多的话还可以用短信 多了的话就很容易给人发现的了!另外,这里用的是Activity而非Service,就是说要打开这个Activity, 才可以进行监听,通常我们的需求都是要偷偷滴在后台跑的,因为时间关系就不写Service的了,如果需要 可自行修改,让Service随开机一起启动即可! **代码解析**: > 很简单,其实就是重写TelephonyManager的一个通话状态监听器**PhoneStateListener** 然后调用TelephonyManager.listen()的方法进行监听,当来电的时候, 程序就会将来电号码记录到文件中! **实现代码**: **MainActivity.java**: ~~~ public class MainActivity extends Activity { TelephonyManager tManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 取得TelephonyManager对象 tManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); // 创建一个通话状态监听器 PhoneStateListener listener = new PhoneStateListener() { @Override public void onCallStateChanged(int state, String number) { switch (state) { // 无任何状态 case TelephonyManager.CALL_STATE_IDLE: break; case TelephonyManager.CALL_STATE_OFFHOOK: break; // 来电铃响时 case TelephonyManager.CALL_STATE_RINGING: OutputStream os = null; try { os = openFileOutput("phoneList", MODE_APPEND); } catch (FileNotFoundException e) { e.printStackTrace(); } PrintStream ps = new PrintStream(os); // 将来电号码记录到文件中 ps.println(new Date() + " 来电:" + number); ps.close(); break; default: break; } super.onCallStateChanged(state, number); } }; // 监听电话通话状态的改变 tManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); } } ~~~ **运行结果**: 注意!要让这个程序位于前台哦!用另一个电话拨打该电话,接着就可以在DDMS的file Explorer的应用 对应包名的files目录下看到phoneList的文件了,我们可以将他导出到电脑中打开,文件的大概内容如下: THR Oct 30 12:05:48 GMT 2014 来电: 137xxxxxxx 对了,别忘了权限! ~~~ <!-- 授予该应用读取通话状态的权限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> ~~~ * * * ### 5)黑名单来电自动挂断 > 所谓的黑名单就是将一些电话号码添加到一个集合中,当手机接收到这些电话的时候就直接挂断! 但是Android并没有给我们提供挂断电话的API,于是乎我们需要通过AIDL来调用服务中的API来 实现挂断电话! > > 于是乎第一步要做的就是把android源码中的下面两个文件复制到src下的相应位置,他们分别是: com.android.internal.telephony包下的**ITelephony.aidl**; > > android.telephony包下的**NeighboringCellInfo.aidl**; > > 要创建对应的包哦!就是要把aidl文件放到上面的包下!!! 接着只需要调用ITelephony的endCall即可挂断电话! 这里给出的是简单的单个号码的拦截,输入号码,点击屏蔽按钮后,如果此时屏蔽的电话呼入的话; 直接会挂断,代码还是比较简单的,下面粘一下,因为用的模拟器是Genymotion,所以就不演示 程序运行后的截图了! **MainActivity.java**: ~~~ public class MainActivity extends Activity { private TelephonyManager tManager; private PhoneStateListener pListener; private String number; private EditText locknum; private Button btnlock; public class PhonecallListener extends 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: if(isBlock(incomingNumber)) { try { Method method = Class.forName("android.os.ServiceManager") .getMethod("getService", String.class); // 获取远程TELEPHONY_SERVICE的IBinder对象的代理 IBinder binder = (IBinder) method.invoke(null, new Object[] { TELEPHONY_SERVICE }); // 将IBinder对象的代理转换为ITelephony对象 ITelephony telephony = ITelephony.Stub.asInterface(binder); // 挂断电话 telephony.endCall(); }catch(Exception e){e.printStackTrace();} } break; } super.onCallStateChanged(state, incomingNumber); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); locknum = (EditText) findViewById(R.id.locknum); btnlock = (Button) findViewById(R.id.btnlock); //获取系统的TelephonyManager管理器 tManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); pListener = new PhoneStateListener(); tManager.listen(pListener, PhoneStateListener.LISTEN_CALL_STATE); btnlock.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { number = locknum.getText().toString(); } }); } public boolean isBlock(String phone) { if(phone.equals(number))return true; return false; } } ~~~ **权限,权限,权限**: ~~~ <!-- 授予该应用控制通话的权限 --> <uses-permission android:name="android.permission.CALL_PHONE" /> <!-- 授予该应用读取通话状态的权限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> ~~~ 另外,关于相关属性与方法中文版可见:[Android电话信息相关API](http://www.linuxidc.com/Linux/2011-10/45049.htm) * * * ## 3.本节示例代码下载 [TelephonyManagerDemo.zip](http://static.runoob.com/download/TelephonyManagerDemo.zip) [TelephonyManagerDemo2.zip](http://static.runoob.com/download/TelephonyManagerDemo2.zip) [黑名单拦截Demo.zip](http://static.runoob.com/download/%E9%BB%91%E5%90%8D%E5%8D%95%E6%8B%A6%E6%88%AADemo.zip) * * * ## 本节小结: > 好的,本节关于TelephonyManager(电话管理器)的学习就到这里,应该已经涵盖了 大部分的开发需求的了,如果有什么遗漏的,欢迎提出~ > > 谢谢~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79b0c5a66.jpg)
';

第十章——系统服务

最后更新于:2022-04-01 05:29:55

';

9.4 使用MediaRecord录音

最后更新于:2022-04-01 05:29:53

## 本节引言 本节是Android多媒体基本API调用的最后一节,带来的是MediaRecord的简单使用, 用法非常简单,我们写个例子来熟悉熟悉~ * * * ## 1.使用MediaRecord录制音频 **运行结果**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ae6c90e.jpg) **实现代码**: 布局代码:**activity_main.xml**: ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/btn_control" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始录音" /> </RelativeLayout> ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity { private Button btn_control; private boolean isStart = false; private MediaRecorder mr = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_control = (Button) findViewById(R.id.btn_control); btn_control.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(!isStart){ startRecord(); btn_control.setText("停止录制"); isStart = true; }else{ stopRecord(); btn_control.setText("开始录制"); isStart = false; } } }); } //开始录制 private void startRecord(){ if(mr == null){ File dir = new File(Environment.getExternalStorageDirectory(),"sounds"); if(!dir.exists()){ dir.mkdirs(); } File soundFile = new File(dir,System.currentTimeMillis()+".amr"); if(!soundFile.exists()){ try { soundFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } mr = new MediaRecorder(); mr.setAudioSource(MediaRecorder.AudioSource.MIC); //音频输入源 mr.setOutputFormat(MediaRecorder.OutputFormat.AMR_WB); //设置输出格式 mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_WB); //设置编码格式 mr.setOutputFile(soundFile.getAbsolutePath()); try { mr.prepare(); mr.start(); //开始录制 } catch (IOException e) { e.printStackTrace(); } } } //停止录制,资源释放 private void stopRecord(){ if(mr != null){ mr.stop(); mr.release(); mr = null; } } } ~~~ 最后别忘了在AndroidManifest.xml中添加下述权限: ~~~ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> ~~~ 好的,就是这么简单~ * * * ## 2.本节示例代码下载 [RecordDemo.zip](http://static.runoob.com/download/RecordDemo.zip) * * * ## 本节小结: > 好的,本节内容非常简单,就是MediaRecorder的使用而已,大概是整套教程中最精简的一节 了吧~嘿嘿~![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ae7d603.jpg)
';

9.3 使用Camera拍照

最后更新于:2022-04-01 05:29:50

## 本节引言 本节给大家带来的是Android中Camera的使用,简单点说就是拍照咯,无非两种: 1.调用系统自带相机拍照,然后获取拍照后的图片 2.要么自己写个拍照页面 本节我们来写两个简单的例子体验下上面的这两种情况~ * * * ## 1.调用系统自带Carema 我们只需下面一席话语,即可调用系统相机,相机拍照后会返回一个intent给onActivityResult。 intent的extra部分包含一个编码过的Bitmap~ ~~~ Intent it = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(it,Activity.DEFAULT_KEYS_DIALER); //重写onActivityResult方法 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == Activity.RESULT_OK){ Bundle bundle = data.getExtras(); Bitmap bitmap = (Bitmap) bundle.get("data"); img_show.setImageBitmap(bitmap); } } ~~~ **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ac869fa.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ad1b9c4.jpg) 这模糊的AV画质...毕竟是编码过后的Bitmap,对了,拍完的图片是不会保存到本地的, 我们可以自己写代码把图片保存到我们的SD卡里,然后再显示,这样的图片会清晰很多, 嗯,我们写代码来试下~ ~~~ //定义一个保存图片的File变量 private File currentImageFile = null; //在按钮点击事件处写上这些东西,这些是在SD卡创建图片文件的: @Override public void onClick(View v) { File dir = new File(Environment.getExternalStorageDirectory(),"pictures"); if(dir.exists()){ dir.mkdirs(); } currentImageFile = new File(dir,System.currentTimeMillis() + ".jpg"); if(!currentImageFile.exists()){ try { currentImageFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } Intent it = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); it.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(currentImageFile)); startActivityForResult(it, Activity.DEFAULT_KEYS_DIALER); } //onActivityResult: @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == Activity.DEFAULT_KEYS_DIALER) { img_show.setImageURI(Uri.fromFile(currentImageFile)); } } ~~~ 好的,非常简单,我们来看下运行结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ad42a3f.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ada15e0.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79adbde4d.jpg) 相比起上面那个清晰多了~调用系统自带Carema就是这么简单~ * * * ## 2.自己写一个拍照页面 这里我们需要用一个SurfaceView作为我们的预览界面,使用起来同一非常简单! **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79add3406.jpg) **代码实现**: 布局代码:**activity_main.xml**:一个简单的surfaceView + Button ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <SurfaceView android:id="@+id/sfv_preview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:id="@+id/btn_take" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="调用系统照相机" /> </LinearLayout> ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity { private SurfaceView sfv_preview; private Button btn_take; private Camera camera = null; private SurfaceHolder.Callback cpHolderCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { startPreview(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { stopPreview(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { sfv_preview = (SurfaceView) findViewById(R.id.sfv_preview); btn_take = (Button) findViewById(R.id.btn_take); sfv_preview.getHolder().addCallback(cpHolderCallback); btn_take.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { camera.takePicture(null, null, new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { String path = ""; if ((path = saveFile(data)) != null) { Intent it = new Intent(MainActivity.this, PreviewActivity.class); it.putExtra("path", path); startActivity(it); } else { Toast.makeText(MainActivity.this, "保存照片失败", Toast.LENGTH_SHORT).show(); } } }); } }); } //保存临时文件的方法 private String saveFile(byte[] bytes){ try { File file = File.createTempFile("img",""); FileOutputStream fos = new FileOutputStream(file); fos.write(bytes); fos.flush(); fos.close(); return file.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); } return ""; } //开始预览 private void startPreview(){ camera = Camera.open(); try { camera.setPreviewDisplay(sfv_preview.getHolder()); camera.setDisplayOrientation(90); //让相机旋转90度 camera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } //停止预览 private void stopPreview() { camera.stopPreview(); camera.release(); camera = null; } } ~~~ 最后是另外一个PreviewActivity.java,这里将图片显示到界面上而已~ ~~~ /** * Created by Jay on 2015/11/22 0022. */ public class PreviewActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ImageView img = new ImageView(this); String path = getIntent().getStringExtra("path"); if(path != null){ img.setImageURI(Uri.fromFile(new File(path))); } setContentView(img); } } ~~~ 嗯,都非常简单哈,别忘了加上权限: ~~~ <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ~~~ 另外,有一点要说的就是假如carema没有释放掉的话,那么下次调用carema就不会报错, 报错内容是:java.lang.RuntimeException:fail to connect to camera service 所以,需要对Carema进行release();假如一直报上面的错误,请重启手机~ * * * ## 3.本节示例代码下载 [CaremaDemo1.zip](http://static.runoob.com/download/CaremaDemo1.zip) [CaremaDemo2.zip](http://static.runoob.com/download/CaremaDemo2.zip) * * * ## 本节小结 > 好的,本节给大家讲解了如何去调用系统自带相机获取拍照后的图片,以及自己写Carema来 完成自定义相机,嘿嘿,在某些场合下我们不需要拍照预览界面,我们直接把弄一个悬浮框, 然后点击悬浮框,就触发拍照事件,这不就可以实现什么不知鬼不觉的拍摄了么?(偷拍) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79ae1ebc7.jpg)嘿嘿,有点意思,要嗨自己动手写代码~
';

9.2 MediaPlayer播放音频与视频

最后更新于:2022-04-01 05:29:48

## 本节引言: > 本节带来的是Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频 该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码 和播放音视频。它支持三种不同的媒体来源: > > * 本地资源 > * 内部的URI,比如你可以通过ContentResolver来获取 > * 外部URL(流) 对于Android所支持的的媒体格式列表 > > 对于Android支持的媒体格式列表,可见:[Supported Media Formats](http://androiddoc.qiniudn.com/guide/appendix/media-formats.html) 文档 > > 本节我们就来用MediaPlayer来写个简单的播放音视频的例子! > > 官方API文档:[MediaPlayer](http://androiddoc.qiniudn.com/reference/android/media/MediaPlayer.html) * * * ## 1.相关方法详解 * * * ### 1)获得MediaPlayer实例: 可以**直接new**或者调用**create**方法创建: ~~~ MediaPlayer mp = new MediaPlayer(); MediaPlayer mp = MediaPlayer.create(this, R.raw.test); //无需再调用setDataSource ~~~ 另外create还有这样的形式: **create(Context context, Uri uri, SurfaceHolder holder)** 通过Uri和指定 SurfaceHolder 【抽象类】 创建一个多媒体播放器 * * * ### 2)设置播放文件: ~~~ //①raw下的资源: MediaPlayer.create(this, R.raw.test); //②本地文件路径: mp.setDataSource("/sdcard/test.mp3"); //③网络URL文件: mp.setDataSource("http://www.xxx.com/music/test.mp3"); ~~~ > 另外setDataSource()方法有多个,里面有这样一个类型的参数:FileDescriptor,在使用这个 API的时候,需要把文件放到res文件夹平级的assets文件夹里,然后使用下述代码设置DataSource: ~~~ AssetFileDescriptor fileDescriptor = getAssets().openFd("rain.mp3"); m_mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(), fileDescriptor.getLength()); ~~~ * * * ### 3)其他方法 > * **getCurrentPosition**( ):得到当前的播放位置 > * **getDuration**() :得到文件的时间 > * **getVideoHeight**() :得到视频高度 > * **getVideoWidth**() :得到视频宽度 > * **isLooping**():是否循环播放 > * **isPlaying**():是否正在播放 > * **pause**():暂停 > * **prepare**():准备(同步) > * **prepareAsync**():准备(异步) > * **release()**:释放MediaPlayer对象 > * **reset**():重置MediaPlayer对象 > * **seekTo(int msec)**:指定播放的位置(以毫秒为单位的时间) > * **setAudioStreamType(int streamtype)**:指定流媒体的类型 > * **setDisplay(SurfaceHolder sh)**:设置用SurfaceHolder来显示多媒体 > * **setLooping(boolean looping)**:设置是否循环播放 > * **setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)**: 网络流媒体的缓冲监听 > * **setOnCompletionListener(MediaPlayer.OnCompletionListener listener)**: 网络流媒体播放结束监听 > * **setOnErrorListener(MediaPlayer.OnErrorListener listener)**: 设置错误信息监听 > * **setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener)**: 视频尺寸监听 > * **setScreenOnWhilePlaying(boolean screenOn)**:设置是否使用SurfaceHolder显示 > * **setVolume(float leftVolume, float rightVolume)**:设置音量 > * **start**():开始播放 > * **stop**():停止播放 * * * ## 2.使用代码示例 ### **示例一**:使用MediaPlayer播放音频: **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79abd0d42.jpg) **关键代码**: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private Button btn_play; private Button btn_pause; private Button btn_stop; private MediaPlayer mPlayer = null; private boolean isRelease = true; //判断是否MediaPlayer是否释放的标志 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { btn_play = (Button) findViewById(R.id.btn_play); btn_pause = (Button) findViewById(R.id.btn_pause); btn_stop = (Button) findViewById(R.id.btn_stop); btn_play.setOnClickListener(this); btn_pause.setOnClickListener(this); btn_stop.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_play: if(isRelease){ mPlayer = MediaPlayer.create(this,R.raw.fly); isRelease = false; } mPlayer.start(); //开始播放 btn_play.setEnabled(false); btn_pause.setEnabled(true); btn_stop.setEnabled(true); break; case R.id.btn_pause: mPlayer.pause(); //停止播放 btn_play.setEnabled(true); btn_pause.setEnabled(false); btn_stop.setEnabled(false); break; case R.id.btn_stop: mPlayer.reset(); //重置MediaPlayer mPlayer.release(); //释放MediaPlayer isRelease = true; btn_play.setEnabled(true); btn_pause.setEnabled(false); btn_stop.setEnabled(false); break; } } } ~~~ 注意事项: > 播放的是res/raw目录下的音频文件,创建MediaPlayer调用的是create方法,第一次启动播放前 不需要再调用prepare(),如果是使用构造方法构造的话,则需要调用一次prepare()方法! 另外贴下官方文档中,从其他两种途径播放音频的示例代码: **本地Uri**: ~~~ Uri myUri = ....; // initialize Uri here MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDataSource(getApplicationContext(), myUri); mediaPlayer.prepare(); mediaPlayer.start(); ~~~ **外部URL**: ~~~ String url = "http://........"; // your URL here MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDataSource(url); mediaPlayer.prepare(); // might take long! (for buffering, etc) mediaPlayer.start(); ~~~ > **Note**:假如你通过一个URL以流的形式播放在线音频文件,该文件必须可以进行 渐进式下载 * * * ### **示例二**:使用MediaPlayer播放视频 MediaPlayer主要用于播放音频,没有提供图像输出界面,所以我们需要借助其他的 组件来显示MediaPlayer播放的图像输出,我们可以使用用**SurfaceView** 来显示,下面我们使用SurfaceView来写个视频播放的例子: **运行效果图**: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-02_565e79abee856.jpg) **实现代码**: 布局文件:**activity_main.xml** ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp"> <SurfaceView android:id="@+id/sfv_show" android:layout_width="match_parent" android:layout_height="300dp" /> <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始" /> <Button android:id="@+id/btn_pause" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="暂停 " /> <Button android:id="@+id/btn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="终止" /> </LinearLayout> ~~~ **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener, SurfaceHolder.Callback { private MediaPlayer mPlayer = null; private SurfaceView sfv_show; private SurfaceHolder surfaceHolder; private Button btn_start; private Button btn_pause; private Button btn_stop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { sfv_show = (SurfaceView) findViewById(R.id.sfv_show); btn_start = (Button) findViewById(R.id.btn_start); btn_pause = (Button) findViewById(R.id.btn_pause); btn_stop = (Button) findViewById(R.id.btn_stop); btn_start.setOnClickListener(this); btn_pause.setOnClickListener(this); btn_stop.setOnClickListener(this); //初始化SurfaceHolder类,SurfaceView的控制器 surfaceHolder = sfv_show.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setFixedSize(320, 220); //显示的分辨率,不设置为视频默认 } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_start: mPlayer.start(); break; case R.id.btn_pause: mPlayer.pause(); break; case R.id.btn_stop: mPlayer.stop(); break; } } @Override public void surfaceCreated(SurfaceHolder holder) { mPlayer = MediaPlayer.create(MainActivity.this, R.raw.lesson); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setDisplay(surfaceHolder); //设置显示视频显示在SurfaceView上 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} @Override public void surfaceDestroyed(SurfaceHolder holder) {} @Override protected void onDestroy() { super.onDestroy(); if (mPlayer.isPlaying()) { mPlayer.stop(); } mPlayer.release(); } } ~~~ 代码很简单,布局有个SurfaceView,然后调用getHolder获得一个SurfaceHolder对象, 在这里完成SurfaceView相关的设置,设置了显示的分辨率以及一个Callback接口, 重写了SurfaceView创建时,发生变化时,以及销毁时的三个方法!然后按钮控制播放 以及暂停而已~ * * * ### 示例三:使用VideoView播放视频 除了使用MediaPlayer + SurfaceView播放视频的方式,我们还可以使用VideoView来直接 播放视频,我们稍微改点东西就可以实现视频播放!运行效果和上面的一致,就不贴了, 直接上代码! **MainActivity.java**: ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener { private VideoView videoView; private Button btn_start; private Button btn_pause; private Button btn_stop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { videoView = (VideoView) findViewById(R.id.videoView); btn_start = (Button) findViewById(R.id.btn_start); btn_pause = (Button) findViewById(R.id.btn_pause); btn_stop = (Button) findViewById(R.id.btn_stop); btn_start.setOnClickListener(this); btn_pause.setOnClickListener(this); btn_stop.setOnClickListener(this); //根据文件路径播放 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/lesson.mp4"); } //读取放在raw目录下的文件 //videoView.setVideoURI(Uri.parse("android.resource://com.jay.videoviewdemo/" + R.raw.lesson)); videoView.setMediaController(new MediaController(this)); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_start: videoView.start(); break; case R.id.btn_pause: videoView.pause(); break; case R.id.btn_stop: videoView.stopPlayback(); break; } } } ~~~ 代码非常简单,就不解释了~有疑问的自己下个Demo运行下即可~ * * * ## 3.本节示例代码下载: [MediaPlayerDemo.zip](http://static.runoob.com/download/MediaPlayerDemo.zip) [MediaPlayerDemo2.zip](http://static.runoob.com/download/MediaPlayerDemo2.zip) [VideoViewDemo.zip](http://static.runoob.com/download/VideoViewDemo.zip) * * * ## 本节小结: > 好的,本节跟大家简单的介绍了下如何使用MediaPlayer播放音频以及结合SurfaceView 来播放视频,最后还写了一个用VideoView播放视频的例子,都是些非常简单的用法~ 相信大家学习起来非常简单~嗯,谢谢~
';