Wakelock、AlarmManager、JobScheduler实例分析

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

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);
    }

}
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);

    }
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());
    }


}
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);
        }
    }
}
<?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这个新的广播接收者。

 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才能执行代码。

 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在指定规则下执行任务

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);
        }
    }

}
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);
        }
    }
}
 <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>

Last updated