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播放视频的例子,都是些非常简单的用法~ 相信大家学习起来非常简单~嗯,谢谢~