从 Android 静音看正确的查找 bug 的姿势
生活随笔
收集整理的這篇文章主要介紹了
从 Android 静音看正确的查找 bug 的姿势
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
0、寫在前面
沒搶到小馬哥的紅包,無心回家了,回公司寫篇文章安慰下自己TT。。話說年關難過,bug多多,時間久了難免頭昏腦熱,不辨朝暮,難識乾坤。。。艾瑪,扯遠了,話說誰沒踩過坑,可視大家都是如何從坑里爬出來的呢?
1、實現個靜音的功能
| private void setMuteEnabled(boolean enabled){ ????AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); ????mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, enabled); } |
2、『您好,我是京東快遞,您有一個bug簽收一下』
話說,過了幾天, QA:『如果我先開啟靜音,然后退出我們的app再進來,盡管頁面顯示靜音狀態,但我無法取消靜音啊』 RD:『一定是你的用法有問題!』 當然,我也挺心虛的啊,因為這段代碼我總共花了三分鐘,說有bug,我也不敢不信吶。我們再來細細把剛才的場景理一遍: 1.??打開app,開啟靜音 2. 點擊返回鍵,直到app進入后臺運行 3. 重新點擊app的icon,啟動app,此時期望app中的靜音按鈕顯示為靜音開啟的狀態,并且點擊可以取消靜音。當然,實際上并不是這樣 (|_|) 有問題需要提一下,Android api并沒有提供獲取當前音頻通道是否靜音的api(為什么沒有?你。。你居然問我為什么?你為什么這么著急?往后看就知道啦),所以我在進入app加載view時,要根據本地存儲的靜音狀態來初始化view的狀態: [Java]?純文本查看?復制代碼 ?| boolean persistedMute = mute.getContext().getSharedPreferences("volume", Context.MODE_PRIVATE).getBoolean("Volume.Mute", false); muteButton.setChecked(persistedMute); |
接著看,這時候我們要取消靜音了,調用的代碼就是下面這段代碼: [Java]?純文本查看?復制代碼 ?
| private void setMuteEnabled(boolean enabled){ ????AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); ????mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, enabled); } |
3、『你可以告訴我該靜音或者不靜音,但聽不聽那是我的事兒』
我這么無辜,寥寥幾行代碼,能犯什么錯誤呢?所以問題一定出在官方的API上。 AudioManager.java [Java]?純文本查看?復制代碼 ?
| 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); ????} } |
4、『這是我的名片』
突然,嗯,就是在這時,我想起前幾天我那本被茶水泡了的《深入理解Android》卷③提到,其實每個app都可以發送靜音請求,而且各自都是單獨計數的。那么問題來了,每個app發靜音請求的唯一身份標識是啥嘞? 還是要看設置靜音的接口方法: AudioManager.java [Java]?純文本查看?復制代碼 ?| 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 death private int mMuteCount; // Number of active mutes for this client VolumeDeathHandler(IBinder cb) { ????mICallback = cb; } …… } |
5、『其實,剛才不是我』
對呀,有名片啊,問題是我這是同一個app啊,同一個啊……問題出在哪里了呢。 剛才我們知道了,其實靜音請求計數是以AudioManager當中的一個叫mICallBack的家伙為唯一標識的,這個家伙是哪里來的呢??
AudioManager.java [Java]?純文本查看?復制代碼 ?| 1 | private final IBinder mICallBack = new Binder(); |
| 1 | AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); |
?
ContextImpl.java [Java]?純文本查看?復制代碼 ?| 1 2 3 4 5 | @Override public Object getSystemService(String name) { ????ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); ????return fetcher == null ? null : fetcher.getService(this); } |
? ???
[Java]?純文本查看?復制代碼 ?| 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); ??????????}}); |
| 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代碼跳轉方面的支持已經非常厲害了。
轉載于:https://www.cnblogs.com/krislight1105/p/5203164.html
總結
以上是生活随笔為你收集整理的从 Android 静音看正确的查找 bug 的姿势的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GetLastError()函数返回值及
- 下一篇: redis集群安装和java应用