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
参考资料:
- https://developer.android.com/reference/android/app/job/JobService、
- https://www.cnblogs.com/mingfeng002/p/8359573.html、
- https://developer.android.com/topic/performance/background-optimization
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