業(yè)務描述
公司項目是做IM即時通訊的蚣抗,在項目開發(fā)的過程中遇到這樣一個需求:語音播放的場景下侈百,當手機靠近面部時,顯示屏熄滅翰铡,語音播放自動切換為聽筒模式钝域,當手機遠離面部時,語音播放切換為揚聲器模式锭魔。
實現(xiàn)思考
其實原理很簡單例证,android手機一般都有距離感應裝置,根據(jù)距離感應裝置的相應回調(diào)參數(shù)去做聽筒迷捧,揚聲器织咧,和屏幕點亮熄滅的操作。但是在開發(fā)中還是遇到了很多坑漠秋,下面會一一陳述烦感。
代碼開發(fā)
屏幕喚醒鎖(WakeLock)
先說說這個WakeLock
powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, this.getClass().getName());
可以看出WakeLock是PowerManager的一個內(nèi)部類。先說newWakeLock(int levelAndFlags, String tag)這個方法膛堤,改方法創(chuàng)建一個新的喚醒鎖手趣,需要兩個參數(shù),levelAndFlags是喚醒鎖的類型,第二個參數(shù)tag就是WakeLock的一個tag绿渣。
在一個頁面中可以有一個或者多個WakeLock,只要有一個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.
簡單就是說中符,當距離傳感器感應到距離接近時就會關(guān)閉屏幕姜胖,當遠離時就會關(guān)閉屏幕。
屏幕點亮調(diào)用 wakeLock.acquire();
屏幕熄滅調(diào)用 wakeLock.release();
這里還有一個方法
wakeLock.setReferenceCounted(false); // 設(shè)置不啟用引用計數(shù)
屏幕鎖有一個機制淀散,在設(shè)置引用計數(shù)的情況下(wakeLock.setReferenceCounted(true)其實系統(tǒng)默認的就是true),這時候wakeLock.acquire()和wakeLock.release()是需要成對出現(xiàn)的右莱,也就是說兩個方法的調(diào)用次數(shù)要相同,否則wakeLock就不能釋放档插,影響正常的操作慢蜓。如果wakeLock.setReferenceCounted(false),則不啟用引用計數(shù)郭膛,無論你調(diào)用了多少次wakeLock.acquire()晨抡,只需要一個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()中,有一個判斷條件(!mRefCounted || mCount++ == 0)棍现。
- mRefCounted為false時才會調(diào)用屏幕管理進程去點亮屏幕调煎,在不做任何設(shè)置的情況下,默認該值是true的己肮。也就是主要看下一個判斷條件
- mCount++ == 0 為true時才能調(diào)用到屏幕管理進程點亮屏幕
在看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è)置了引用計數(shù),在設(shè)置了的情況下朴肺,點亮屏幕的操作與熄滅屏幕的操作次數(shù)是否相等窖剑。
到此坚洽,屏幕鎖的使用及原理就說完了
下面直接看距離感應的相關(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(); // 點亮屏幕
switchToSpeaker(); // 切換到揚聲器
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
},sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY 是獲取一個距離傳感器類型戈稿,SensorManager.SENSOR_DELAY_NORMAL 是一個傳感器靈敏度的參數(shù),選擇普通靈敏度讶舰。
至此鞍盗,根據(jù)距離傳感器實現(xiàn)語音切換,屏幕點亮熄滅功能的基本實現(xiàn)都已講完(主干實現(xiàn))跳昼。在開發(fā)過程中還是遇到了不少的坑的般甲。這里說幾點開發(fā)中需要注意的問題:
- 使用wakelock.release()熄滅屏幕并不會走Activity的生命周期(本人親測),所以不要指望在屏幕熄滅的時候在生命周期回調(diào)中做一些工作鹅颊,但是點bakc鍵還是會正常的回調(diào)生命周期敷存。
- 記得在activity的生命周期中unregisterListener,解注冊掉感應器。
- 如果沒解注冊掉感應器锚烦,或者wakelock沒有合理的使用(任何一著)都會導致你回退到上個界面觅闽,或者切到桌面,wakelock和感應鎖依然正常工作涮俄,影響其他功能的正常使用蛉拙,我就是在wakelock的使用上耽誤了一天。
結(jié)尾貼一下聽筒揚聲器切換的代碼
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);
}
(完)