標籤雲

搜尋此網誌

2015/12/07

Best Practices for Background Jobs - Keeping the Device Awake

當 android device 被閒置的時候
它會先變暗,關閉螢幕,最後關閉 CPU 以節省電源
但有時我們需要螢幕長時間開啟(如遊戲或影片)
或不需要螢幕開啟但是希望保持 CPU 運作直到某些操作完成
就需要一些方法

Keep the Screen On
設定 FLAG_KEEP_SCREEN_ON (只能在 Activity 而不能在 service 或其他 app component)
public class MainActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  }
這不需要特別的 permission 而且使用者在不同 app 間切換時我們不需要擔心資源會被釋放掉
另一個方法是在 layout xml 檔案中使用 android:keepScreenOn 屬性
這等同於使用 FLAG_KEEP_SCREEN_ON
但是若之後需要將 flag 移掉的話用 FLAG_KEEP_SCREEN_ON 會比較方便
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true">
    ...
</RelativeLayout>

Keep the CPU On
如果要保持 CPU 持續運作以完成某些工作
可以用 WAKE_LOCK 這個 PowerManager 的 system service
但因為這很容易讓電池電量被榨乾
所以官方強烈建議只在必要時適當使用(例如不應該在 activity 中使用,要保持 activity 螢幕開啟應使用 FLAG_KEEP_SCREEN_ON)

其他可選的方案有:
1. 如果 app 要進行長時間的 HTTP download,使用 DownloadManager
2. 如果 app 要從外部 server 同步資料,那就建立一個 sync adapter
3. 如果 app 要依賴 background service,使用 repeating alarms 或 Google Cloud Messaging 來觸發

要使用 WAKE_LOCK 首先要宣告 uses-permission
<uses-permission android:name="android.permission.WAKE_LOCK" />

然後建議的方法是使用 WakefulBroadcastReceiver 來管理 WAKE_LOCK
不過如果不是用這方法可以直接用 code 設定
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");
wakeLock.acquire();
//釋放 wake lock
wakelock.release()

接下來就來介紹 WakefulBroadcastReceiver

WakefulBroadcastReceiver 是一種特別的 broadcast receiver
它負責創建並管理我們 app 中的 PARTIAL_WAKE_LOCK
將工作傳給 service (通常是 IntentService)並確保 device 不會休眠
如果我們沒有持有 wake lock 就把工作丟給 service 就無法確保 device 不會在工作完成前就休眠

使用 WakefulBroadcastReceiver 的第一步是在 manifest 宣告
<receiver android:name=".MyWakefulReceiver"></receiver>

接著使用 startWakefulService 就可以在 service 啟動時持有 wake lock
傳入的 Intent 包含一個 extra 用來識別 wake lock

public class MyWakefulReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        // Start the service, keeping the device awake while the service is
        // launching. This is the Intent to deliver to the service.
        Intent service = new Intent(context, MyIntentService.class);
        startWakefulService(context, service);
    }
}

當 service 完成時會呼叫 WakefulBroadcastReceiver 的 completeWakefulIntent() 以釋放 wake lock
public class MyIntentService extends IntentService {
    public static final int NOTIFICATION_ID = 1;
    private NotificationManager mNotificationManager;
    NotificationCompat.Builder builder;

    public MyIntentService() {
        super("MyIntentService");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        // 進行一些工作
        ...(省略)
        // 最後釋放 WakefulBroadcastReceiver 提供的 wake lock
        MyWakefulReceiver.completeWakefulIntent(intent);
    }
}

相關資料:
Managing Device Awake State
Keeping the Device Awake

沒有留言: