版權聲明:本文出自汪磊的博客,轉載請務必注明出處。
一、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" />
注意:在使用該類的時候,必須保證acquire和release是成對出現的。
三、保持屏幕常亮
最好的方式是在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,不要做電池殺手。
聲明:文章將會陸續搬遷到個人公眾號,以后文章也會第一時間發布到個人公眾號,及時獲取文章內容請關注公眾號

