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

 

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