Android語音開發(fā)-聽筒揚(yáng)聲器自動(dòng)切換

業(yè)務(wù)描述

公司項(xiàng)目是做IM即時(shí)通訊的,在項(xiàng)目開發(fā)的過程中遇到這樣一個(gè)需求:語音播放的場景下,當(dāng)手機(jī)靠近面部時(shí),顯示屏熄滅,語音播放自動(dòng)切換為聽筒模式,當(dāng)手機(jī)遠(yuǎn)離面部時(shí),語音播放切換為揚(yáng)聲器模式。

實(shí)現(xiàn)思考

其實(shí)原理很簡單,android手機(jī)一般都有距離感應(yīng)裝置,根據(jù)距離感應(yīng)裝置的相應(yīng)回調(diào)參數(shù)去做聽筒,揚(yáng)聲器,和屏幕點(diǎn)亮熄滅的操作。但是在開發(fā)中還是遇到了很多坑,下面會一一陳述。

代碼開發(fā)

屏幕喚醒鎖(WakeLock)

先說說這個(gè)WakeLock

powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, this.getClass().getName());

可以看出WakeLock是PowerManager的一個(gè)內(nèi)部類。先說newWakeLock(int levelAndFlags, String tag)這個(gè)方法,改方法創(chuàng)建一個(gè)新的喚醒鎖,需要兩個(gè)參數(shù),levelAndFlags是喚醒鎖的類型,第二個(gè)參數(shù)tag就是WakeLock的一個(gè)tag。

在一個(gè)頁面中可以有一個(gè)或者多個(gè)WakeLock,只要有一個(gè)WakeLock持有著屏幕,則屏幕不會熄滅

在newWakeLoke方法中我傳入了PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK參數(shù),關(guān)于該參數(shù)的描述

Wake lock level: Turns the screen off when the proximity sensor activates.
* If the proximity sensor detects that an object is nearby, the screen turns off
* immediately. Shortly after the object moves away, the screen turns on again.

簡單就是說,當(dāng)距離傳感器感應(yīng)到距離接近時(shí)就會關(guān)閉屏幕,當(dāng)遠(yuǎn)離時(shí)就會關(guān)閉屏幕。
屏幕點(diǎn)亮調(diào)用 wakeLock.acquire();
屏幕熄滅調(diào)用 wakeLock.release();
這里還有一個(gè)方法

wakeLock.setReferenceCounted(false); // 設(shè)置不啟用引用計(jì)數(shù)

屏幕鎖有一個(gè)機(jī)制,在設(shè)置引用計(jì)數(shù)的情況下(wakeLock.setReferenceCounted(true)其實(shí)系統(tǒng)默認(rèn)的就是true),這時(shí)候wakeLock.acquire()和wakeLock.release()是需要成對出現(xiàn)的,也就是說兩個(gè)方法的調(diào)用次數(shù)要相同,否則wakeLock就不能釋放,影響正常的操作。如果wakeLock.setReferenceCounted(false),則不啟用引用計(jì)數(shù),無論你調(diào)用了多少次wakeLock.acquire(),只需要一個(gè)wakeLock.release()就可以釋放屏幕鎖。

這里貼一下acquire(),release()的代碼

public void acquire() {
    synchronized (mToken) {
        acquireLocked();
    }
}
private void acquireLocked() {
        if (!mRefCounted || mCount++ == 0) {
                         mHandler.removeCallbacks(mReleaser);
            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
            try {
                mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
                        mHistoryTag);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
        }
        mHeld = true;
    }
}

acquire()方法中調(diào)用了acquireLocked(),在acquireLocked()中,有一個(gè)判斷條件(!mRefCounted || mCount++ == 0)。

  1. mRefCounted為false時(shí)才會調(diào)用屏幕管理進(jìn)程去點(diǎn)亮屏幕,在不做任何設(shè)置的情況下,默認(rèn)該值是true的。也就是主要看下一個(gè)判斷條件
  2. mCount++ == 0 為true時(shí)才能調(diào)用到屏幕管理進(jìn)程點(diǎn)亮屏幕

在看release()的源碼

 public void release(int flags) {
    synchronized (mToken) {
        if (!mRefCounted || --mCount == 0) {
            mHandler.removeCallbacks(mReleaser);
            if (mHeld) {
                Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
                try {
                    mService.releaseWakeLock(mToken, flags);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
                mHeld = false;
            }
        }
        if (mCount < 0) {
            throw new RuntimeException("WakeLock under-locked " + mTag);
        }
    }
}

同acquire的判斷條件相同,是否設(shè)置了引用計(jì)數(shù),在設(shè)置了的情況下,點(diǎn)亮屏幕的操作與熄滅屏幕的操作次數(shù)是否相等。
到此,屏幕鎖的使用及原理就說完了
下面直接看距離感應(yīng)的相關(guān)代碼

sensorManager.registerListener(new SensorEventListener() {
                @Override
                public void onSensorChanged(SensorEvent event) {
                    float[] dis = event.values;
                    if (0.0f == dis[0]){ // 靠近身體
                        wakeLock.release(); // 熄滅屏幕
                        switchToEarpiece(); // 切換到聽筒
                    }else {
                        wakeLock.acquire(); // 點(diǎn)亮屏幕
                        switchToSpeaker(); // 切換到揚(yáng)聲器
                    }
                }

                @Override
                public void onAccuracyChanged(Sensor sensor, int accuracy) {

                }
            },sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),SensorManager.SENSOR_DELAY_NORMAL);

sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY 是獲取一個(gè)距離傳感器類型,SensorManager.SENSOR_DELAY_NORMAL 是一個(gè)傳感器靈敏度的參數(shù),選擇普通靈敏度。

至此,根據(jù)距離傳感器實(shí)現(xiàn)語音切換,屏幕點(diǎn)亮熄滅功能的基本實(shí)現(xiàn)都已講完(主干實(shí)現(xiàn))。在開發(fā)過程中還是遇到了不少的坑的。這里說幾點(diǎn)開發(fā)中需要注意的問題:

  1. 使用wakelock.release()熄滅屏幕并不會走Activity的生命周期(本人親測),所以不要指望在屏幕熄滅的時(shí)候在生命周期回調(diào)中做一些工作,但是點(diǎn)bakc鍵還是會正常的回調(diào)生命周期。
  2. 記得在activity的生命周期中unregisterListener,解注冊掉感應(yīng)器。
  3. 如果沒解注冊掉感應(yīng)器,或者wakelock沒有合理的使用(任何一著)都會導(dǎo)致你回退到上個(gè)界面,或者切到桌面,wakelock和感應(yīng)鎖依然正常工作,影響其他功能的正常使用,我就是在wakelock的使用上耽誤了一天。

結(jié)尾貼一下聽筒揚(yáng)聲器切換的代碼

if (!speakerphoneOn){
            mAudioManager.setSpeakerphoneOn(false);
            mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
        }else {
            mAudioManager.setMode(AudioManager.MODE_NORMAL);
            mAudioManager.setSpeakerphoneOn(true);
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}

(完)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容