service 执行定时任务

app 开启定时任务

1.普通service + 定时任务

可以在界面开启一个service,在结合timer 、TimerTask 完成,比如

 Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                    Log.d(TAG, "run: " );
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
 timer.schedule(task, 0, 2000);

 2. jobService 、JobScheduler

参考资料:

Activity 中启动jobService 

  JobScheduler mJobScheduler;

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

        mJobScheduler = (JobScheduler) getSystemService( Context.JOB_SCHEDULER_SERVICE );
    }

    @TargetApi(21)
    public void startJobSchduler(View view){

        ComponentName componentName = new ComponentName(this, MyJobService.class);
        int id = (int)(Math.random()*100+1);
        Log.d(TAG, "startJobSchduler: " + id);
        JobInfo.Builder jobInfo = new JobInfo.Builder(id, componentName);
        if (Build.VERSION.SDK_INT >= 24){
            jobInfo.setMinimumLatency(2000)
                    .setOverrideDeadline(2000)
                    .setBackoffCriteria(2000,  JobInfo.BACKOFF_POLICY_LINEAR);

        }else {
            jobInfo .setPeriodic(2000);
        }
        jobInfo.setPersisted(true)
                .setRequiresCharging(true)
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                 .setRequiresDeviceIdle(false);
        int result = mJobScheduler.schedule(jobInfo.build());
        Log.d(TAG, "startJobSchduler: " + result);
    }

JobService 

private Handler mHandler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if (msg.what == 1){

                Toast.makeText(MyJobService.this, "handle task is running", Toast.LENGTH_SHORT).show();
                Log.d(TAG, "handleMessage: " + 1);
                //jobFinished((JobParameters) msg.obj, true);
                jobFinished((JobParameters) msg.obj, true);
            }else if(msg.what == 2){
                Log.d(TAG, "handleMessage: " + 2);
            }
        }
    };

    @Override
    public boolean onStartJob(final JobParameters params) {
        Log.d(TAG, "onStartJob: ");
        Toast.makeText(MyJobService.this, "task is running", Toast.LENGTH_SHORT).show();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(TAG, "run: ");
                Message message = Message.obtain();
                message.what = 1;
                message.obj = params;
                mHandler.sendMessage(message);
            }
        }).start();

        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        Log.d(TAG, "onStopJob: ");
        return false;
    }

jobService 的相关方法介绍:

onStrartJob : 该方法运行在主线程(所以耗时任务要开子线程),在此方法中执行你的任务,如果你需要持续执行你的任务,就需要返回true ,同时jobFinished方法需要被调用;返回false 表示你的任务执行完毕,那么onStopJob 就不会被调用。 

onStopJob : 当用户调用取消任务时,比如 schulder.cancle (jobId) , 该返回会被调用,或者任务不满足一些设定的条件时候,也会被停止。返回true, 任务会按照 之前设置的 backoffCertic 策略 重新执行;

  • setMinimumLatency(long minLatencyMillis)——延迟执行时间 (毫秒单位)
  • setOverrideDeadline(long maxExecutionDelayMillis)——设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动
  • setRequiresDeviceIdle(boolean )——是否在空闲状态下执行
  • setRequiresCharging(boolean requiresCharging)——是否当设备在充电时这个任务才会被执行
  • setRequiredNetworkType(int networkType)——设置可执行的网络类型,一般选择NETWORK_TYPE_ANY (任意类型);wifi 情况下执行NETWORK_TYPE_UNMETERED
  • setPeriodic(long time)设置任务间隔时间 ,注意:该方法不能和setMinimumLatnecy \SetOverrideDeadline 一起用,否则会抛异常,具体原因可以看源码;

jobService 在7.0 中需要如上面说写那样才能运行,否则对于定时任务不起作用。

缺陷:

 只有5.0 之后才引用这个类,之前的没有该api,故5.0 之前的手机会有问题;

3. AlarmManager 

mainActivity

        ComponentName reciver = new ComponentName(this, AlarmReceiver.class);
        PackageManager pm = getPackageManager();

        pm.setComponentEnabledSetting(reciver,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);

        mAlarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
        Intent intent = new Intent(this,  AlarmReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
        mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,  System.currentTimeMillis(), 3000, pendingIntent);
        Log.d(TAG, "alarmManager: ");

 AlarmReceiver

    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive: ");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Log.d(TAG, "onReceive: O");
            context.startForegroundService(new Intent(context, NormalService.class));
        } else {
            Log.d(TAG, "onReceive: not O");
            context.startService(new Intent(context, NormalService.class));
        }
    }

Service

  @Override
    public void onCreate() {
        super.onCreate();

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            setForegroundService();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    Log.d(TAG, "run: 1 s");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        return super.onStartCommand(intent, flags, startId);
    }


 private static final String CHANNEL_ID = "com.appname.notification.channel";
    /**
     *创建通知渠道
     */
    private void createNotificationChannel() {
        // 在API>=26的时候创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //设定的通知渠道名称
            String channelName = "8.0 前台服务";
            //设置通知的重要程度
            int importance = NotificationManager.IMPORTANCE_LOW;
            //构建通知渠道
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, channelName, importance);
            channel.setDescription("8.0 前台服务 的 内容");
            channel.setSound(null, null);
            channel.enableVibration(false);
            //向系统注册通知渠道,注册后不能改变重要性以及其他通知行为
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }

   
 public void  setForegroundService() {
        //在创建的通知渠道上发送通知
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
        builder.setSmallIcon(R.mipmap.ic_launcher) //设置通知图标
                .setContentTitle("test title")//设置通知标题
                .setContentText("test  content")//设置通知内容
                .setAutoCancel(true) //用户触摸时,自动关闭
                .setOngoing(true);//设置处于运行状态

        createNotificationChannel();
        //将服务置于启动状态 NOTIFICATION_ID指的是创建的通知的ID
        startForeground(1, builder.build());

    }

代码解读:

  mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,  System.currentTimeMillis(), 3000, pendingIntent); 较为重要,第一个参数有如下值可取:

  •    ELAPSED_REALTIME       让定时任务的触发时间从系统开机开始算起,但不唤醒CPU 相对时间
  •    ELAPSED_REALTIME_WAKEUP      让定时任务的触发时间从系统开机开始算起,但会唤醒CPU 相对时间
  •    RTC    让定时任务的触发时间从1970年1月1日0点开始算起,但不唤醒CPU 绝对时间
  •    RTC_WAKEUP     让定时任务的触发时间从1970年1月1日0点开始算起,但会唤醒CPU 绝对时间
  •    POWER_OFF_WAKEUP  表示闹钟在手机【关机】状态下也能正常进行提示功能,用绝对时间,但某些版本并不支持!绝对时间

第二个参数取的时间,看第一个参数使用是绝对时间还是相对时间。绝对时间取 System.currentTimeMills , 相对时间如下:

 //表示闹钟(首次)执行时间。相对于系统启动时间,Returns milliseconds since boot, including time spent in sleep  
        long triggerAtTime = SystemClock.elapsedRealtime() + 3 * 1000;  

manifest 配置

 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

 <service android:name=".NormalService"
            android:enabled="true"
            android:exported="true">

        </service>

        <receiver
            android:name=".AlarmReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

 

 

一般的,重复任务用alarm Manager 实现,推荐使用  setInexactRepeating ,同时setRepeating其最小时间间隔为 1 min ; 另外,alarmManager 用于执行间隔时间很长的定时任务,间隔时间太短,频繁使用,耗电量会增加很多。

app关闭后,AlarmManager 还会作用吗?

 我自己测试过,不起作用。但是按照上面写的代码,在pixels 3 手机上,把app从最近运行列表中删除,定时任务还是会执行。有的手机则不行,因为pixel3手机为Android 9,这时候app 会创建一个前台service,把app从列表中删除后,其实这个时候前台service还是在运行的,这样其实感觉不算是真正的杀掉应用。但是在国产的大于8的版本手机则不行,这里,不知道有什么点在里面。不知道有没有人研究过这个东西,有知道可以说一下,但是从网上的其他资料好像是可以的,我不太确定。另外,这里说要监听开机广播,因为设备重启后,闹钟会被清除,但这和 app关闭后,service能否运行没有什么关系?

参考链接:https://stackoverflow.com/questions/47425668/alarm-manager-not-working-when-app-closed?rq=1 

service 保活

1. 前台进程

  • set(int type,long startTime,PendingIntent pi):一次性闹钟
  • setRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 重复性闹钟,和setInexactRepeating有区别,setInexactRepeating闹钟间隔时间不固定
  • setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 重复性闹钟,时间不固定
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            Intent intent = new Intent(this, NormalService.class);
            startForegroundService(intent);
        }else {
            Intent intent = new Intent(this, NormalService.class);
            startService(intent);
        }

如何判断进程是否为前台进程? 官网说明

系统可以区分前台后台应用。 (用于 Service 限制目的的后台定义与内存管理使用的定义不同;一个应用按照内存管理的定义可能处于后台,但按照能够启动 Service 的定义又处于前台。)如果满足以下任意条件,应用将被视为处于前台:

  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  • 具有前台 Service。
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service;

2. 进程守护

 这个东西,不推荐,也不太好。自己也不太熟悉,原因是耗电量增加,和其他的一些问题。后面自己研究一下。

3. 提高优先级

  在manifest 中增加 <intent-filter propertity = 1000> , 数值越大越高。

4. 通过推送唤醒应用,这些感觉也不太好。到时候自己写一个demo。

 

相关一些工具:

查看后台哪些服务在运行:

public static boolean isServiceRunning(Context context, String ServiceName) {
    if (TextUtils.isEmpty(ServiceName)) {
        return false;
    }
    ActivityManager myManager = (ActivityManager) context
            .getSystemService(Context.ACTIVITY_SERVICE);
    ArrayList<ActivityManager.RunningServiceInfo> runningService = (ArrayList<ActivityManager.RunningServiceInfo>) myManager
            .getRunningServices(30);
    for (int i = 0; i < runningService.size(); i++) {
        if (runningService.get(i).service.getClassName().toString()
                .equals(ServiceName)) {
            return true;
        }
    }
    return false;
}

相关参考:

高版本对后台service限制:https://developer.android.com/about/versions/oreo/background.html

 

JobService 深入分析: https://www.cnblogs.com/mingfeng002/p/8359573.html
Jobservice 简单介绍:https://www.jianshu.com/p/8f9090e12015

AlarmManager官网介绍 :
 https://developer.android.com/reference/android/app/AlarmManager、https://developer.android.com/training/scheduling/alarms#precision

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章