業(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)。
- mRefCounted為false時(shí)才會調(diào)用屏幕管理進(jìn)程去點(diǎn)亮屏幕,在不做任何設(shè)置的情況下,默認(rèn)該值是true的。也就是主要看下一個(gè)判斷條件
- 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ā)中需要注意的問題:
- 使用wakelock.release()熄滅屏幕并不會走Activity的生命周期(本人親測),所以不要指望在屏幕熄滅的時(shí)候在生命周期回調(diào)中做一些工作,但是點(diǎn)bakc鍵還是會正常的回調(diào)生命周期。
- 記得在activity的生命周期中unregisterListener,解注冊掉感應(yīng)器。
- 如果沒解注冊掉感應(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);
}
(完)