转载:Evernote带来的计划任务神器Android-Job

原文地址:Evernote带来的计划任务神器Android-Job

在新版本安卓版本(API23以上)中,谷歌把android.net.conn.CONNECTIVITY_CHANGE 这个广播废弃了。总的来说,应用不应该依赖这个广播来执行相关任务,而应该使用 JobScheduler 或者 GCMNetworkManager。计划任务的执行方案还可以用 AlarmManager。 对于国内的开发者来说,这是一个悲剧。Android 现在提供这三种不同的API来执行计划任务.这三种API都有各自的优缺点 。 android.net.conn.CONNECTIVITY_CHANGE 在高版本的系统中不可用了,JobScheduler 在低版本的系统中又不可用,GCMNetworkManager 是什么?在国内不存在。而且这些API之间的不同语义和用法对开发者是一种额外的负担。这种负担体现在我们不仅仅要懂得什么时候使用合适的API,而且要为不同的环境执行同样任务提供不同分支的代码。APP需要在运行时候用合适的条件判断到底要使用哪种方式执行计划任务。幸运的是,开源的世界有伟大的公司为我们提供了灵活的轮子。

Evernote 开源的 Android-Job 为我们带来兼容这三种API的方案,高效,简单,灵活。Android-Job 在运行判断使用哪种API,它提供 AlarmManager, JobSchedulerGcmNetworkManager功能的超集,比如说,我们可以定义计划任务在网络连通且在充电时候执行。

为兼容而生

上面提到的三种方案,兼容维护非常麻烦。为了彰显Android-Job的价值,我们有必要对这三种方案做一个分析。

AlarmManager

AlarmManager 的API一直在改变 ,所以我们需要非常注意我们APP目标运行系统是哪个版本。

优点

  • 所有设备上可用
  • 发送广播来启动一个服务执行计划很容易

缺点

  • API在不同系统版本表现不一致
  • 太多样板代码
  • 忽略了设备的状态
示意图

JobScheduler

JobScheduler 只在Lollipop以上版本可用。

优点

  • 流式API简洁易用
  • 检查设备状态

缺点

  • 只在API 21+ 可用 (有些功能要求API24+)
  • 平台bug
  • 也是太多样板代码

GcmNetworkManager

GcmNetworkManager 只有在安装有Google Play的设备上可用,而且,没有被墙。

优点

  • JobScheduler相似的API
  • 要求minSdkVersion 9

缺点

  • 是 GooglePlay ServicesSDK 的一部分
  • 不能脱离 Play Services 运行
  • API 有很多陷阱

例子

Android-Job项目作者在一篇PPT中详细的说明了这三种方案以及Android-Job的用法。由于篇幅有限且排版原因,强烈建议点击链接去看看。例子简单易懂,在这里给出PPT对应的PDF文件。还在犹豫是否要使用 Android-Job ,看了这个PDF,相信你心里就有肯定的答案了 PDF传送门

使用Android-Job(翻译自项目说明)

请保证点击以下链接检查你使用的是否是最新版本Android-Job最新版链接
添加以下的Gradle配置到安卓项目:

// 在app 项目的 build.gradle 文件:
dependencies {
    compile 'com.evernote:android-job:1.1.7'
}

如果没有在 Gradle build tools 中关闭 manifest merger,那就不用更多步骤来配置这个工具库了。否则需要手动添加权限生命和services,参考这个AndroidManifest

用法

JobManager 类作为入口。我们自定义的Jobs 需要继承 Job 类。使用对应的Builder类创建一个 JobRequest 然后使用 JobManager 创建这个计划请求。

使用 JobManager 之前必须向初始化这个单例。我们需要提供一个 Context 然后添加一个 JobCreator 实现类.JobCreator 把一个job tag 映射到一个特定的job 类。推荐在 ApplicationonCreate() 方法中 初始化 JobManager 。如果没有 Application 类的访问权限,这是可选的方案

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        JobManager.create(this).addJobCreator(new DemoJobCreator());
    }
}
public class DemoJobCreator implements JobCreator {
    @Override
    public Job create(String tag) {
        switch (tag) {
            case DemoSyncJob.TAG:
                return new DemoSyncJob();
            default:
                return null;
        }
    }
}

然后我们可以开始计划要执行的任务。

public class DemoSyncJob extends Job {
    public static final String TAG = "job_demo_tag";
    @Override
    @NonNull
    protected Result onRunJob(Params params) {
        // run your job here
        return Result.SUCCESS;
    }
    public static void scheduleJob() {
        new JobRequest.Builder(DemoSyncJob.TAG)
                .setExecutionWindow(30_000L, 40_000L)
                .build()
                .schedule();
    }
}

进阶用法

JobRequest.Builder 类有很多额外的配置选项,例如,我们可以声明条件:需要网络连接。为了使任务周期性执行,用 bundle 传递额外的参数,使得任务在重启后或在特定的时间恢复。
每一个Job 有一个唯一的 ID 。这个 ID 帮助我们辨别 job ,以便将来更新条件或者取消 job。

private void scheduleAdvancedJob() {
    PersistableBundleCompat extras = new PersistableBundleCompat();
    extras.putString("key", "Hello world");
    int jobId = new JobRequest.Builder(DemoSyncJob.TAG)
            .setExecutionWindow(30_000L, 40_000L)
            .setBackoffCriteria(5_000L, JobRequest.BackoffPolicy.EXPONENTIAL)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(false)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
            .setExtras(extras)
            .setRequirementsEnforced(true)
            .setPersisted(true)
            .setUpdateCurrent(true)
            .build()
            .schedule();
}
private void schedulePeriodicJob() {
    int jobId = new JobRequest.Builder(DemoSyncJob.TAG)
            .setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
            .setPersisted(true)
            .build()
            .schedule();
}
private void scheduleExactJob() {
    int jobId = new JobRequest.Builder(DemoSyncJob.class)
            .setExact(20_000L)
            .setPersisted(true)
            .build()
            .schedule();
}
private void cancelJob(int jobId) {
    JobManager.instance().cancel(jobId);
}

如果非周期性的 Job 执行失败,我们可以设定恢复标准重新计划这个 job

public class RescheduleDemoJob extends Job {
    @Override
    @NonNull
    protected Result onRunJob(Params params) {
        // 一些异常情况发生,稍后重试这个Job
        return Result.RESCHEDULE;
    }
    @Override
    protected void onReschedule(int newJobId) {
        // 恢复的Job有一个新的 ID
    }
}

提醒:谷歌在安卓 Marshmallow 版本引入自动备份功能,所有的 job 信息保存在一个文件名为 evernote_jobs.xml的 shared preference , 以及文件名为evernote_jobs.db 的数据库中. 我们应该排除这些文件,避免他们被自动备份。
我们可以定义一个resource XML 文件 (比如, res/xml/backup_config.xml) 内容如下:

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <exclude domain="sharedpref" path="evernote_jobs.xml" />
    <exclude domain="database" path="evernote_jobs.db" />
</full-backup-content>

然后在应用的 AndroidManifest.xml指向这个文件:

<application ...  android:fullBackupContent="@xml/backup_config">

代码混淆

这个库没有使用反射,但是它依赖两个 Service 以及两个 BroadcastReceiver 为了避免任何可能出现的问题,不应该混淆这四个类。这个库打包了它自己的混淆配置,所以我们不需要做任何额外的配置。以防万一,也可以在我们项目的混淆配置中添加这些规则

FAQ

点击这里查看更多的疑问解答。

参考资料

evernote的Blog
作者Ralf的PPT

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容