安卓電量優化之WakeLock鎖機制全面解析


版權聲明:本文出自汪磊的博客,轉載請務必注明出處。

一、WakeLock概述

wakelock是一種鎖的機制,只要有應用拿着這個鎖,CPU就無法進入休眠狀態,一直處於工作狀態。比如,手機屏幕在屏幕關閉的時候,有些應用依然可以喚醒屏幕提示用戶消息,這里就是用到了wakelock鎖機制,雖然手機屏幕關閉了,但是這些應用依然在運行着。手機耗電的問題,大部分是開發人員沒有正確使用這個鎖,成為"待機殺手"。

Android手機有兩個處理器,一個叫Application Processor(AP),一個叫Baseband Processor(BP)。AP是ARM架構的處理器,用於運行Linux+Android系統;BP用於運行實時操作系統(RTOS),通訊協議棧運行於BP的RTOS之上。非通話時間,BP的能耗基本上在5mA左右,而AP只要處於非休眠狀態,能耗至少在50mA以上,執行圖形運算時會更高。另外LCD工作時功耗在100mA左右,WIFI也在100mA左右。一般手機待機時,AP、LCD、WIFI均進入休眠狀態,這時Android中應用程序的代碼也會停止執行。

Android為了確保應用程序中關鍵代碼的正確執行,提供了Wake Lock的API,使得應用程序有權限通過代碼阻止AP進入休眠狀態。但如果不領會Android設計者的意圖而濫用Wake Lock API,為了自身程序在后台的正常工作而長時間阻止AP進入休眠狀態,就會成為待機電池殺手。

那么Wake Lock API具體有啥用呢?心跳包從請求到應答,斷線重連重新登陸等關鍵邏輯的執行過程,就需要Wake Lock來保護。而一旦一個關鍵邏輯執行成功,就應該立即釋放掉Wake Lock了。兩次心跳請求間隔5到10分鍾,基本不會怎么耗電。

二、WakeLock使用

獲取WakeLock實例代碼如下:

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);  
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");

newWakeLock(int levelAndFlags, String tag)中PowerManager.PARTIIAL_WAKE_LOCK是一個標志位,標志位是用來控制獲取的WakeLock對象的類型,主要控制CPU工作時屏幕是否需要亮着以及鍵盤燈需要亮着,標志位說明如下:

levelAndFlags CPU是否運行 屏幕是否亮着 鍵盤燈是否亮着
PARTIAL_WAKE_LOCK
SCREEN_DIM_WAKE_LOCK 低亮度
SCREEN_BRIGHT_WAKE_LOCK 高亮度
FULL_WAKE_LOCK

特殊說明:自API等級17開始,FULL_WAKE_LOCK將被棄用。應用應使用FLAG_KEEP_SCREEN_ON

WakeLock類可以用來控制設備的工作狀態。使用該類中的acquire可以使CPU一直處於工作的狀態,如果不需要使CPU處於工作狀態就調用release來關閉。

(1)、自動release

如果我們調用的是acquire(long timeout)那么就無需我們自己手動調用release()來釋放鎖,系統會幫助我們在timeout時間后釋放。

(2)、手動release

如果我們調用的是acquire()那么就需要我們自己手動調用release()來釋放鎖。

最后使用WakeLock類記得加上如下權限:

1 <uses-permission android:name="android.permission.WAKE_LOCK" />   

注意:在使用該類的時候,必須保證acquirerelease是成對出現的。

三、保持屏幕常亮

最好的方式是在Activity中使用FLAG_KEEP_SCREEN_ON的Flag。

1 public class MainActivity extends Activity { 2  @Override 3     protected void onCreate(Bundle savedInstanceState) { 4         super.onCreate(savedInstanceState); 5  setContentView(R.layout.activity_main); 6  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 7  } 8 }

這個方法的好處是不像喚醒鎖(wake locks),需要一些特定的權限(permission)。並且能正確管理不同app之間的切換,不用擔心無用資源的釋放問題。 
另一個方式是在布局文件中使用android:keepScreenOn屬性:

1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 
3     android:layout_width="match_parent"
4     android:layout_height="match_parent"
5     android:keepScreenOn="true">
6  ... 7 </RelativeLayout>

android:keepScreenOn = ”true“的作用和FLAG_KEEP_SCREEN_ON一樣。使用代碼的好處是你允許你在需要的地方關閉屏幕。

注意:一般不需要人為的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager會管理好程序進入后台回到前台的的操作。如果確實需要手動清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

四、WakefulBroadcastReceiver + IntentService實例

IntentService使用請參照我之前博客Android IntentService使用介紹以及源碼解析

WakefulBroadcastReceiver是BroadcastReceiver的一種特例。它會為你的APP創建和管理一個PARTIAL_WAKE_LOCK類型的WakeLock。WakefulBroadcastReceiver把工作交接給service(通常是IntentService),並保證交接過程中設備不會進入休眠狀態。如果不持有WakeLock,設備很容易在任務未執行完前休眠。最終結果是你的應用不知道會在什么時候能把工作完成,相信這不是你想要的。

使用startWakefulService()方法來啟動服務,與startService()相比,在啟動服務的同時,並啟用了喚醒鎖。

當后台服務的任務完成,要調用WLWakefulReceiver.completeWakefulIntent()來釋放喚醒鎖。

WLWakefulReceiver類如下:

 1 public class WLWakefulReceiver extends WakefulBroadcastReceiver {  2 
 3     private static final String TAG = "myTag";  4 
 5  @Override  6     public void onReceive(Context context, Intent intent) {  7         //  8         String extra = intent.getStringExtra("msg");  9         Log.i(TAG, "onReceive:"+extra); 10         Intent serviceIntent = new Intent(context, MyIntentService.class); 11         serviceIntent.putExtra("msg", extra); 12  startWakefulService(context, serviceIntent); 13  } 14 }

很簡單,就是打印一下信息以及調用startWakefulService方法來啟動服務。

MyIntentService類如下:

 1 public class MyIntentService extends IntentService {  2 
 3     private static final String TAG = "myTag";  4     
 5     public MyIntentService() {  6         super("MyIntentService");  7  }  8 
 9  @Override 10     protected void onHandleIntent(Intent intent) { 11         //子線程中執行
12         Log.i(TAG, "onHandleIntent"); 13         for (int i = 0; i < 10; i++) { 14             try { 15                 Thread.sleep(3000); 16                 String extra = intent.getStringExtra("msg"); 17                 Log.i(TAG, "onHandleIntent:"+extra); 18             } catch (InterruptedException e) { 19  e.printStackTrace(); 20  } 21  } 22         //調用completeWakefulIntent來釋放喚醒鎖。
23  WLWakefulReceiver.completeWakefulIntent(intent); 24  } 25 }

同樣很簡單,也是打印信息進行耗時操作,但是執行完自己業務邏輯后一點記得調用completeWakefulIntent來釋放喚醒鎖。

最后就是啟動廣播接收者以及加入權限和聲明了:

Intent intent = new Intent("WANG_LEI"); intent.putExtra("msg", "學習WAKE_LOCK。。。"); sendBroadcast(intent); <uses-permission android:name="android.permission.WAKE_LOCK" />

<receiver android:name=".WLWakefulReceiver" >
            <intent-filter>
                <action android:name="WANG_LEI" />
            </intent-filter>
</receiver>
<service android:name=".MyIntentService"></service>

好了,編寫好程序運行發現及時按電源鍵屏幕關閉依然有LOG打印出。

本篇到此結束,wakelock鎖主要是相對系統的休眠而言的,意思就是我的程序給CPU加了這個鎖那系統就不會休眠了,這樣做的目的是為了全力配合我們程序的運行。有的情況如果不這么做就會出現一些問題,比如微信等及時通訊的心跳包會在熄屏不久后停止網絡訪問等問題。所以微信里面是有大量使用到了wake_lock鎖。希望經過上述共同學習你能正確使用WakeLock,不要做電池殺手。

聲明:文章將會陸續搬遷到個人公眾號,以后文章也會第一時間發布到個人公眾號,及時獲取文章內容請關注公眾號

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM