# Wakelock、AlarmManager、JobScheduler实例分析

## 后台定时唤醒App做指定任务实例

```java
public class MainActivity extends Activity implements View.OnClickListener {

    private Button btnStartalarm;

    private SyncDataRecevier mSyncDataRecevier = new SyncDataRecevier();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnStartalarm = (Button)findViewById(R.id.btn_startalarm);
        btnStartalarm.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_startalarm:
                mSyncDataRecevier.setAlarm(this);
                break;
        }
    }

    public void stopAlarm(View view){
        mSyncDataRecevier.cancelAlarm(this);
    }

}
```

```java
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.support.v4.content.WakefulBroadcastReceiver;

import chenxiaojian.backgroundtask.service.SyncDataService;


public class SyncDataRecevier extends WakefulBroadcastReceiver {

    private AlarmManager mAlarmManager;
    private PendingIntent mPendingAlarmIntent;

    @Override
    public void onReceive(Context context, Intent intent) {
//        ComponentName comp = new ComponentName(context.getPackageName(),
//                SyncDataRecevier.class.getName());
//        startWakefulService(context,intent.setComponent(comp));

        Intent service = new Intent(context, SyncDataService.class);
        startWakefulService(context, service);
    }

    public void setAlarm(Context context) {
        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, SyncDataRecevier.class);
        mPendingAlarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

        //五分钟后，每隔五分钟fired一次。
//        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, AlarmManager
//                .INTERVAL_FIFTEEN_MINUTES, AlarmManager.INTERVAL_FIFTEEN_MINUTES,
//                mPendingAlarmIntent);

        //30秒后，每隔30s就启动一次
        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock
                .elapsedRealtime() + 1 * 1000, 10 * 1000, mPendingAlarmIntent);

        //系统重启后依然要重新启动Alarm
        ComponentName componentName = new ComponentName(context,BootRecevier.class);
        PackageManager pm = context.getPackageManager();

        pm.setComponentEnabledSetting(componentName,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
    }

    public void cancelAlarm(Context context){
        if (mAlarmManager != null){
            mAlarmManager.cancel(mPendingAlarmIntent);
        }

        // Disable {@code SampleBootReceiver} so that it doesn't automatically restart the
        // alarm when the device is rebooted.
        ComponentName receiver = new ComponentName(context, BootRecevier.class);
        PackageManager pm = context.getPackageManager();

        pm.setComponentEnabledSetting(receiver,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);

    }
```

```java
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;

import chenxiaojian.backgroundtask.MainActivity;
import chenxiaojian.backgroundtask.R;
import chenxiaojian.backgroundtask.recevier.SyncDataRecevier;


public class SyncDataService extends IntentService {

    private NotificationManager mNotificationManager;
    NotificationCompat.Builder builder;

    // An ID used to post the notification.
    public static final int NOTIFICATION_ID = 1;

    public SyncDataService(){
        super("SyncDataService");
    }


    @Override
    protected void onHandleIntent(Intent intent) {

        //耗时操作
        loadNetData();

        //发送Notifaction通知
        sendNotifation("哈哈来一发");

        //通过这个广播接收器释放锁
        SyncDataRecevier.completeWakefulIntent(intent);

    }

    private void loadNetData() {

    }

    private void sendNotifation(String msg){
        mNotificationManager = (NotificationManager) getSystemService(Context
                .NOTIFICATION_SERVICE);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
                MainActivity.class), 0);
        builder =
                new NotificationCompat.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle("Alarm定时通知")
                        .setStyle(new NotificationCompat.BigTextStyle()
                                .bigText(msg))
                        .setContentText(msg);

        builder.setContentIntent(pendingIntent);

        mNotificationManager.notify(NOTIFICATION_ID, builder.build());
    }


}
```

```java
public class BootRecevier extends BroadcastReceiver {

    private SyncDataRecevier mSyncDataRecevier = new SyncDataRecevier();

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED"))
        {
            mSyncDataRecevier.setAlarm(context);
        }
    }
}
```

```markup
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="chenxiaojian.backgroundtask">


    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="18" />
    <uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service android:name=".service.SyncDataService" android:exported="false"/>
        <receiver android:name=".recevier.SyncDataRecevier" android:exported="false"></receiver>
        <receiver android:name=".recevier.BootRecevier" android:exported="false"></receiver>


    </application>

</manifest>
```

上面的这个实例是一个经典的用AlarmManager的过程，可以看到我们甚至可以在应用休眠的时间执行代码，唤醒CPU，那说明它一定用到了WakeLock锁。可以注意到我们用了WakefulBroadcastReceiver这个新的广播接收者。

```java
 public static ComponentName startWakefulService(Context context, Intent intent) {
        synchronized (mActiveWakeLocks) {
            int id = mNextId;
            mNextId++;
            if (mNextId <= 0) {
                mNextId = 1;
            }

            intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
            ComponentName comp = context.startService(intent);
            if (comp == null) {
                return null;
            }

            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    "wake:" + comp.flattenToShortString());
            wl.setReferenceCounted(false);
            wl.acquire(60*1000);
            mActiveWakeLocks.put(id, wl);
            return comp;
        }
    }
```

分析代码可以知道这个这个Recevier里面有PowerManager.WakeLock，也就是说当AlarmManager被激活后，它会唤起一个PendingIntent。而这个PendingIntent会唤醒这个SyncDataRecevier，这个SyncDataRecevier会拥有CPU锁60s，也就是说，我们的任务应该在60s内完成，为什么非要加这个SyncDataRecevier，虽然我们有AlarmManager可以被执行，但是如果此时AP处理器正在休眠，它依然无法执行代码，也就是说，我们BP是一直是活着的，到一个点之后，它想办法唤醒AP才能执行代码。

```java
 Intent intent = new Intent(context, SyncDataRecevier.class);
        mPendingAlarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock
                .elapsedRealtime() + 1 * 1000, 10 * 1000, mPendingAlarmIntent);
```

## JobScheduler在指定规则下执行任务

```java
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class MainActivity extends AppCompatActivity {
    private ComponentName serviceComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        serviceComponent = new ComponentName(this, JobSchedulerService.class);
    }

    public void startJobScheduler(View view) {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        for (int i = 0; i < 10; i++) {
            JobInfo.Builder builder = new JobInfo.Builder(i, serviceComponent)
                    .setMinimumLatency(5000)//5秒 最小延时、
                    .setOverrideDeadline(60000)//maximum最多执行时间
                    //                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                    // 免费的网络---wifi 蓝牙 USB
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) //任意网络---
                    /**
                     设置重试/退避策略，当一个任务调度失败的时候执行什么样的测量采取重试。
                     initialBackoffMillis:第一次尝试重试的等待时间间隔ms
                     *backoffPolicy:对应的退避策略。比如等待的间隔呈指数增长。
                     */
//                    .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
//                    .setPeriodic(long intervalMillis)//设置执行周期，每隔一段时间间隔任务最多可以执行一次。
//                    .setPeriodic(long intervalMillis,long flexMillis)
// 在周期执行的末端有一个flexMiliis长度的窗口期，任务就可以在这个窗口期执行。
            //设置设备重启后，这个任务是否还要保留。需要权限：RECEIVE_BOOT_COMPLETED //ctrl+shift+y/u x
//                    .setPersisted(boolean isPersisted);
//                    .setRequiresCharging(boolean )//是否需要充电
//                    .setRequiresDeviceIdle(boolean)//是否需要等设备出于空闲状态的时候
//                    .addTriggerContentUri(uri)//监听uri对应的数据发生改变，就会触发任务的执行。
//                    .setTriggerContentMaxDelay(long duration)//设置Content发生变化一直到任务被执行中间的最大延迟时间
            //设置Content发生变化一直到任务被执行中间的延迟。如果在这个延迟时间内content发生了改变，延迟时间会重写计算。
//                    .setTriggerContentUpdateDelay(long durationMilimms)
            .setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR);

            JobInfo jobinfo = builder.build();

            jobScheduler.schedule(jobinfo);
        }
    }

}
```

```java
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;

public class JobSchedulerService extends JobService {
    private static final String LOG_TAG = "MyJobService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(LOG_TAG, "MyJobService created");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(LOG_TAG, "MyJobService destroyed");
    }

//    当任务开始时会执行onStartJob(JobParameters params)方法，因为这是系统用来触发已经被执行的任务。
//    正如你所看到的，这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。
//    如果返回值是true,那么系统假定这个任务正要被执行，执行任务的重担就落在了你的肩上。
//    当任务执行完毕时你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统。
    @Override
    public boolean onStartJob(JobParameters params) {
        // This is where you would implement all of the logic for your job. Note that this runs
        // on the main thread, so you will want to use a separate thread for asynchronous work
        // (as we demonstrate below to establish a network connection).
        // If you use a separate thread, return true to indicate that you need a "reschedule" to
        // return to the job at some point in the future to finish processing the work. Otherwise,
        // return false when finished.
        //在这里可以写你所需要的所有逻辑。因为它是运行在主线程中的，所以你可能需要使用一个单独的线程作一些异步的
        //任务。如果你使用一个单独的线程，返回true来表明你这个工作可能需要在以后某个时间点才能完成，如果你知道它
        //现在就完成了，就直接返回false就行了。
        Log.i(LOG_TAG, "Totally and completely working on job " + params.getJobId());
        // First, check the network, and then attempt to connect.
        if (isNetworkConnected()) {
            new SimpleDownloadTask() .execute(params);
            return true;
        } else {
            Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face");
        }
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // Called if the job must be stopped before jobFinished() has been called. This may
        // happen if the requirements are no longer being met, such as the user no longer
        // connecting to WiFi, or the device no longer being idle. Use this callback to resolve
        // anything that may cause your application to misbehave from the job being halted.
        // Return true if the job should be rescheduled based on the retry criteria specified
        // when the job was created or return false to drop the job. Regardless of the value
        // returned, your job must stop executing.
        Log.i(LOG_TAG, "Whelp, something changed, so I'm calling it on job " + params.getJobId());
        return false;
    }

    /**
     * Determines if the device is currently online.
     */
    private boolean isNetworkConnected() {
        ConnectivityManager connectivityManager =
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        return (networkInfo != null && networkInfo.isConnected());
    }

    /**
     *  Uses AsyncTask to create a task away from the main UI thread. This task creates a
     *  HTTPUrlConnection, and then downloads the contents of the webpage as an InputStream.
     *  The InputStream is then converted to a String, which is logged by the
     *  onPostExecute() method.
     */
    private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> {

        protected JobParameters mJobParam;

        @Override
        protected String doInBackground(JobParameters... params) {
            // cache system provided job requirements
            mJobParam = params[0];
            try {
                InputStream is = null;
                // Only display the first 50 characters of the retrieved web page content.
                int len = 50;

                URL url = new URL("https://www.baidu.com");
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(10000); //10sec
                conn.setConnectTimeout(15000); //15sec
                conn.setRequestMethod("GET");
                //Starts the query
                conn.connect();
                int response = conn.getResponseCode();
                Log.d(LOG_TAG, "The response is: " + response);
                is = conn.getInputStream();

                // Convert the input stream to a string
                Reader reader = null;
                reader = new InputStreamReader(is, "UTF-8");
                char[] buffer = new char[len];
                reader.read(buffer);
                return new String(buffer);

            } catch (IOException e) {
                return "Unable to retrieve web page.";
            }
        }

        @Override
        protected void onPostExecute(String result) {
            jobFinished(mJobParam, false);
            Log.i(LOG_TAG, result);
        }
    }
}
```

```markup
 <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service
            android:name=".JobSchedulerService"
            android:permission="android.permission.BIND_JOB_SERVICE"></service>
    </application>
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xiaojianchen.gitbook.io/performance-optimization/wakelockalarmmanagerjobscheduler-shi-li-fen-xi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
