从 Android 静音看正确的查找 bug 的姿势
0、写在前面
1、实现个静音的功能
PM:『我这里有个需求,很简单很简单那种』RD:『哦,需要做三天』PM:『真的很简单很简单那种』RD:『哦,现在需要做六天了』
private void setMuteEnabled(boolean enabled){ AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, enabled);} |
2、『您好,我是京东快递,您有一个bug签收一下』
QA:『如果我先开启静音,然后退出我们的app再进来,尽管页面显示静音状态,但我无法取消静音啊』RD:『一定是你的用法有问题!』
boolean persistedMute = mute.getContext().getSharedPreferences("volume", Context.MODE_PRIVATE).getBoolean("Volume.Mute", false);muteButton.setChecked(persistedMute); |
接着看,这时候我们要取消静音了,调用的代码就是下面这段代码:
private void setMuteEnabled(boolean enabled){ AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, enabled);} |
我这么无辜,寥寥几行代码,能犯什么错误呢?所以问题一定出在官方的API上。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/** * Mute or unmute an audio stream. * <p> * The mute command is protected against client process death: if a process * with an active mute request on a stream dies, this stream will be unmuted * automatically. * <p> * The mute requests for a given stream are cumulative: the AudioManager * can receive several mute requests from one or more clients and the stream * will be unmuted only when the same number of unmute requests are received. * <p> * For a better user experience, applications MUST unmute a muted stream * in onPause() and mute is again in onResume() if appropriate. * <p> * This method should only be used by applications that replace the platform-wide * management of audio settings or the main telephony application. * <p>This method has no effect if the device implements a fixed volume policy * as indicated by {@link #isVolumeFixed()}. * * @param streamType The stream to be muted/unmuted. * @param state The required mute state: true for mute ON, false for mute OFF * * @see #isVolumeFixed() */public void setStreamMute(int streamType, boolean state) { IAudioService service = getService(); try { service.setStreamMute(streamType, state, mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in setStreamMute", e); }} |
The mute requests for a given stream are cumulative: the AudioManager can receive several mute requests from one or more clients and the stream will be unmuted only when the same number of unmute requests are received.
好像找到答案了。不对呀,我以你的人格担保,我只发了一次静音请求啊,怎么取消静音就这么费劲呢!
4、『这是我的名片』
|
1
2
3
4
5
6
7
8
|
public void setStreamMute(int streamType, boolean state) { IAudioService service = getService(); try { service.setStreamMute(streamType, state, mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in setStreamMute", e); }} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
/** @see AudioManager#setStreamMute(int, boolean) */public void setStreamMute(int streamType, boolean state, IBinder cb) { if (mUseFixedVolume) { return; } if (isStreamAffectedByMute(streamType)) { if (mHdmiManager != null) { synchronized (mHdmiManager) { if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) { synchronized (mHdmiTvClient) { if (mHdmiSystemAudioSupported) { mHdmiTvClient.setSystemAudioMute(state); } } } } } mStreamStates[streamType].mute(cb, state); }} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public void mute(boolean state) {boolean updateVolume = false;if (state) { if (mMuteCount == 0) { // Register for client death notification try { // mICallback can be 0 if muted by AudioService if (mICallback != null) { mICallback.linkToDeath(this, 0); } VolumeStreamState.this.mDeathHandlers.add(this); // If the stream is not yet muted by any client, set level to 0 if (!VolumeStreamState.this.isMuted()) { updateVolume = true; } } catch (RemoteException e) { // Client has died! binderDied(); return; } } else { Log.w(TAG, "stream: "+mStreamType+" was already muted by this client"); } mMuteCount++;} else { if (mMuteCount == 0) { Log.e(TAG, "unexpected unmute for stream: "+mStreamType); } else { mMuteCount--; if (mMuteCount == 0) { // Unregister from client death notification VolumeStreamState.this.mDeathHandlers.remove(this); // mICallback can be 0 if muted by AudioService if (mICallback != null) { mICallback.unlinkToDeath(this, 0); } if (!VolumeStreamState.this.isMuted()) { updateVolume = true; } } }}if (updateVolume) { sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, VolumeStreamState.this, 0); }} |
|
01
02
03
04
05
06
07
08
09
10
|
private class VolumeDeathHandler implements IBinder.DeathRecipient {private IBinder mICallback; // To be notified of client's deathprivate int mMuteCount; // Number of active mutes for this clientVolumeDeathHandler(IBinder cb) { mICallback = cb;}……} |
5、『其实,刚才不是我』
对呀,有名片啊,问题是我这是同一个app啊,同一个啊……问题出在哪里了呢。
|
1
|
private final IBinder mICallBack = new Binder(); |
操曰:『天下英雄,唯使君与操耳』玄德大惊曰:『操耳是哪个嘛?』
|
1
|
AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); |
|
1
2
3
4
5
|
@Overridepublic Object getSystemService(String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); return fetcher == null ? null : fetcher.getService(this);} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public Object getService(ContextImpl ctx) { ArrayList<Object> cache = ctx.mServiceCache; Object service; synchronized (cache) { if (cache.size() == 0) { // Initialize the cache vector on first access. // At this point sNextPerContextServiceCacheIndex // is the number of potential services that are // cached per-Context. for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) { cache.add(null); } } else { service = cache.get(mContextCacheIndex); if (service != null) { return service; } } service = createService(ctx); cache.set(mContextCacheIndex, service); return service; } } |
|
1
2
3
4
|
registerService(AUDIO_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new AudioManager(ctx); }}); |
等会儿让我想会儿静静。它在这里new了一个AudioManager。它怎么能new了一个AudioManager呢。
|
1
|
AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); |
6、『这事儿还是交给同一个人办比较靠谱』
|
1
|
AudioManager mAudioManager = (AudioManager) getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE); |
7、结语
侯捷先生在《STL源码剖析》一书的扉页上面写道『源码之前,了无秘密』。写程序的时候,我经常会因为运行结果与预期不一致而感到不悦,甚至抱怨这就是『命』,想想也是挺逗的。计算机总是会忠实地执行我们提供的程序,如果你发现它『不听』指挥,显然是你的指令有问题;除此之外,我们的指令还需要经过层层传递,才会成为计算机可以执行的机器码,如果你对系统api的工作原理不熟悉,对系统的工作原理不熟悉,你在组织自己的代码的时候就难免一厢情愿。
至于官方API文档,每次看到它都有看到『课本』一样的感觉。中学的时候,老师最爱说的一句话就是,『课本要多读,常读常新』。官方API呢,显然也是这样。没有头绪的时候,它就是我们救星啊。
作为Android开发者,尽管我不需要做Framework开发,但这并不能说明我不需要对Framework有一定的认识和了解。我们应该在平时的开发和学习当中经常翻阅这些系统的源码,了解它们的工作机制有助于我们更好的思考系统api的应用场景。
关于Android系统源码,如果不是为了深入的研究,我比较建议直接在网上直接浏览:
* [Androidxref](http://androidxref.com/),该站点提供了一定程度上的代码跳转支持,以及非常强大的检索功能,是我们查询系统源码的首选。
* [Grepcode](http://grepcode.com/)也可以检索Android系统源码,与前者不同的是,它只包含Java代码,不过也是尺有所长,grepcode在Java代码跳转方面的支持已经非常厉害了。
从 Android 静音看正确的查找 bug 的姿势的更多相关文章
- 从 Android 静音看正确的查bug的姿势?
0.写在前面 没抢到小马哥的红包,无心回家了,回公司写篇文章安慰下自己TT..话说年关难过,bug多多,时间久了难免头昏脑热,不辨朝暮,难识乾坤...艾玛,扯远了,话说谁没踩过坑,可视大家都是如何从坑 ...
- Eclipse导入Android项目的正确方法
转自Eclipse导入Android项目的正确方法 看网上流传的Eclipse导入项目的方法都是在新建Android程序时使用"Create project form existing so ...
- Android应用程序资源的查找过程分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8806798 我们知道,在Android系统中, ...
- [android开发IDE]adt-bundle-windows-x86的一个bug:无法解析.rs文件--------rs_core.rsh file not found
google的android自带的apps写的是相当牛逼的,将其导入到eclipse中方便我们学习扩展.可惜关于导入的资料太少了,尤其是4.1之后的gallery和camera合二为一了.之前导4.0 ...
- 从getApplicationContext和getApplication再次梳理Android的Application正确用法
原文地址http://blog.csdn.net/ly502541243/article/details/52105466 原文地址http://blog.csdn.net/ly502541243/a ...
- android一个下拉放大库bug的解决过程及思考
android一个下拉放大库bug的解决过程及思考 起因 项目中要做一个下拉缩放图片的效果,搜索了下github上面,找到了两个方案. https://github.com/Frank-Zhu/Pul ...
- 7.哪些工具可以帮助查找bug或进行静态分析
哪些工具可以帮助查找bug或进行静态分析? PyChecker is a static analysis tool that detects the bugs in Python source cod ...
- VSCode 在 Vue 导入路径中使用 @ 符号后无法正确跳转 bug
VSCode 在 Vue 导入路径中使用 @ 符号后无法正确跳转 bug bug jsconfig.json { // This file is required for VSCode to unde ...
- 移动端页面 iPhone + Safari 页面调试 之 正确查看网络请求的姿势
如题 本文主要将 Safari + iPhone 前端开发调试 之 正确查看网络请求的 姿势 惯例 说下问题场景: 早知道safari(Mac) + iPhone 调试的方便 能解决很多日常调试问题 ...
随机推荐
- ExtJs store加载
当store加载数据对象中,如果带有success:false,store会认为加载数据失败,不予将得到的数据显示在界面上,所有记得在返回数据对象的同时返回success:true;
- IIS7 发现无法显示ewebeditor编辑器成空白
vs2003写的网站,很早了,编辑器用的是ewebeditor,每次更换程序编辑器都会出问题.今天记录一下. 内部老网站在Windows2003 iis6上运行的. 现在要迁移到2008上64位.08 ...
- 学习块格式化上下文(BlockFormattingContext)
什么是BFC BFC全称是Block Formatting Context,即块格式化上下文.它是CSS2.1规范定义的,关于CSS渲染定位的一个概念.要明白BFC到底是什么,首先来看看什么是视觉格式 ...
- 方法 :PHP开发环境搭建(phpstorm + xampp+mongodb)
phpstorm 安装下载 百度网盘资源 phpstorm 9.0.1(有序列号) http://pan.baidu.com/s/1kTvX0jl xampp 安装下载 ...
- 解决VS2008打开假死或者打开设计模式假死的问题
我昨天刚装完OFFICE 2010一直也没重启,vs2008是可以使用的.今早来到办公室,打开工程发现开了半天VS2008一般空白,查看应用程序显示正在运行,再查看进程发现里面多一个setup.exe ...
- Visual Assist安装、破解方法
1.从各种渠道下载Visual Assist安装程序及破解补丁. 2.在路径(Win7):C:\Users\UserName\AppData\Local\Microsoft\VisualStudio\ ...
- sql触发器知识
触发器中的Inserted和deleted临时表: SQL2000中,inserted表和deleted表用于存放对表中数据行的修改信息.他们是触发器执行时自动创建的,放在内存中,是临时表.当触发器工 ...
- m2e插件的新下载地址
今天在按照<Maven实战>这本书给eclipse配置maven的m2eclipse插件的时候发现,书中写的老的下载地址http://m2eclipse.sonatype.org/site ...
- Sersync实时同步企业应用配置实战
一.实验环境 CentOS版本: 6.6(2.6.32.-504.el6.x86_64) Rsync版本: Rsync-3.0.6(系统自带) Sersync版本:sersync2.5.4_64bi ...
- Python 列表实现字典的get功能
字典有一个很好用的方法,就是get,既可以预防KeyError异常,也可以为不存在的key设置一个默认的value 例如: v=d.get('k','default') 而列表没有一个类似的方法,如果 ...