前言
從事Android framework開發已經兩年了,今天起決定把工作上遇到的問題、做過的需求都用博客的形式記錄一下,特地新開一個系列---Android framework開發工作記錄,有需要的朋友可以參考參考,當然,不保證完全正確,有錯誤的地方歡迎指出,本系列完全是根據工作上的需求來的,看起來肯定是雜亂無章的,請包涵。之前的快應用系列也會接着更新。廢話不多說,下面就進入正題。
需求提出
通過距離感應器實現靠近喚醒,遠離休眠(跟接電話的滅屏亮屏功能正好相反)。拿開之後要求可以延遲休眠,延遲的時間可以動態去配,就像手機上的休眠時間可以在設置中動態選擇一樣15s、30s、1min ......
需求分析
乍一看這個需求很簡單的嘛,但是由於我們公司做的系統的特殊性,就產生了諸多問題,這裏面的細節我就不多說了。對於這個需求我第一個想到的就是,直接監聽一下P-Sensor,靠近的時候發一個power鍵鍵值,遠離的時候再發一個,然後延遲的話就用Handler發一個延遲的消息來做,看起來好像是解決了,but too young, too simple. 測試之後才發現一堆問題,很難受,比如由於人爲因素導致的休眠喚醒就不可控了,像主動按power鍵,連接充電器什麼的亮滅屏。然後就開始嘗試第二種方案,就是借用一下Android原生的休眠機制來做,當然這也是最終的實現方式,下面我們就來具體分析。
需求實現
第一步,對距離感應器進行監聽,這裏我就不詳細解釋了,畢竟重點不在這裏,代碼貼出來:
SensorManager sensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);
Sensor pSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorManager.registerListener(new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
//value值在距感裏面只有兩個,0和另外一個,表示靠近和遠離,不同機器對應關係可能不一樣
float value = event.values[0];
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}, pSensor, SensorManager.SENSOR_DELAY_NORMAL);
主要是在onSensorChanged這個方法裏面對value的值進行監聽。
第二步,利用系統休眠的api來設置休眠時間,代碼如下:
Settings.System.putInt(getContentResolver(),android.provider.Settings.System.SCREEN_OFF_TIMEOUT,15*1000);
當然,我們這個需求是在系統服務中做的 ,普通應用沒有權限的。Settings.System.SCREEN_OFF_TIMEOUT這個值就是表示休眠時間,單位毫秒。所以我們這個需求的實現就是,監聽到距感靠近的時候就發一個WakeUp鍵值喚醒屏幕並且將SCREEN_OFF_TIMEOUT這個值設成很大,遠離的時候就將SCREEN_OFF_TIMEOUT這個值設成我們想要延遲的時間。到這裏基本功能就實現了,但是測試的時候發現連續休眠喚醒會導致延遲的時間不對,跟我們設置的休眠時間有出入,這個時候我們就需要研讀一下系統源碼了,梳理一下系統的休眠機制,來看看到底什麼地方出了問題。
第三步,閱讀源碼,找到上面問題的原因
代碼路徑:frameworks\base\services\core\com\android\server\power\PowerManagerService.java
提到PowerManagerService我們不得不說一個很重要的方法updatePowerStateLocked() ,代碼還是貼一下:
private void updatePowerStateLocked() {
if (!mSystemReady || mDirty == 0) {
return;
}
if (!Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");
try {
// Phase 0: Basic state updates.
updateIsPoweredLocked(mDirty);
updateStayOnLocked(mDirty);
updateScreenBrightnessBoostLocked(mDirty);
// Phase 1: Update wakefulness.
// Loop because the wake lock and user activity computations are influenced
// by changes in wakefulness.
final long now = SystemClock.uptimeMillis();
int dirtyPhase2 = 0;
for (;;) {
int dirtyPhase1 = mDirty;
dirtyPhase2 |= dirtyPhase1;
mDirty = 0;
updateWakeLockSummaryLocked(dirtyPhase1);
updateUserActivitySummaryLocked(now, dirtyPhase1);
if (!updateWakefulnessLocked(dirtyPhase1)) {
break;
}
}
// Phase 2: Update display power state.
boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
// Phase 3: Update dream state (depends on display ready signal).
updateDreamLocked(dirtyPhase2, displayBecameReady);
// Phase 4: Send notifications, if needed.
if (mDisplayReady) {
finishWakefulnessChangeLocked();
}
// Phase 5: Update suspend blocker.
// Because we might release the last suspend blocker here, we need to make sure
// we finished everything else first!
updateSuspendBlockerLocked();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
這個方法的作用就是更新電源的狀態,一會兒再說這個跟我們上面的問題的關係。
因爲原生設置的休眠流程是好的,所以這個時候就需要參考一下了,於是我就去看了設置的源碼,發現它在設置休眠時間的時候也只是調用了我們上面的那個api,這就奇怪了,爲什麼使用的相同的api會出現不同的結果,爲這個問題我也是耗了很長時間,最後終於找到問題的關鍵了,這裏面的不同之處在於我們是直接在系統服務裏面進行的,而設置是用戶操作的,我們的那個操作會導致電源狀態未更新,導致休眠時間和我們設置的有出入,有點小坑額。
第四步,解決bug,完成需求
要解決以上問題我們就需要找到一個能夠更新電源狀態的地方,通過分析我們也是找到了,就在下面這個方法裏面:
private final class SettingsObserver extends ContentObserver {
public SettingsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
synchronized (mLock) {
handleSettingsChangedLocked();
}
}
}
這個onChange方法就能監聽到Settings.System.SCREEN_OFF_TIMEOUT這個值得改變,所以我們在裏面手動調用userActivityInternal()這個方法更新一下電源狀態就行,代碼如下:
private void userActivityInternal(long eventTime, int event, int flags, int uid) {
synchronized (mLock) {
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
updatePowerStateLocked();
}
}
}
參數傳入當前時間就行,
userActivityNoUpdateLocked(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
ok,到此就完美解決了所有問題。由於公司有保密協議,我這裏就不把所有代碼全都貼出來了,有興趣的可以下一份Android源碼自己試試。
總結
其實framework開發中,最難的莫過於閱讀系統源碼,Android源碼中很多service動不動就是上萬行代碼,需要靜下心來把裏面的流程梳理通,只要能做到這一步,很多需求也就沒那麼難了。