Schedule an event to be executed in future (Cron J

2019-03-21 12:12发布

问题:

I have certain dates which once attained lose their relevance and new dates for these fields in the DB should be calculated, I know I can leverage the AlarmManager class for this, however I have a few concerns regarding this:

1) Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.

So do I need to code for both cases separately or if I target kitkat, will that work for older versions too? Also as my code execution is time critical, say after 12AM in the midnight of some date my Data loses relevance, how to overcome shifting of alarms.

2)Registered alarms are retained while the device is asleep (and can optionally wake the device up if they go off during that time), but will be cleared if it is turned off and rebooted.

2.1) Set the RECEIVE_BOOT_COMPLETED permission in your application's manifest. This allows your app to receive the ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting (this only works if the app has already been launched by the user at least once)

2.1.1) If I have set an alarm at 12, the service related to this alarm fires at 12, Now when I reboot the device, the time "at 12" has already passed, the alarm will be fired again immediately and the service will be called again?

At reboot what mechanism do I need to implement in-order to stick to my code execution policy at certain time? How do I set the alarm if the user does not launch my app?

The third thing is that if my app is uninstalled I want to clear all alarms set by my code, how do I listen to when the app is uninstalled?

Also I want to know, my app is very time critical, the values in my DB get obsolete by 12 am each night, while I am updating the app, what would be the result if a user chooses to use my app at 12 while I use a service to update it and its running in the background?

EDIT: What I have tried so far:

I have a Database in which records get stale past midnight, say sharp at 12:00. I invoked an Alarm Manager(In a test project as I like to isolate the problem code) to fire a service. I also acquire a PARTIAL_WAKE_LOCK on the device so that my huge database manipulation is done properly. I have also implemented a thread to do my time consuming task. Following is my MainActivity Class which I invoke at 12 to initiate the alarm(Random time for test purpose):

public class MainActivity extends Activity {
    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    BroadcastReceiver br;
    TextView t; 
    int sum; 


    public void setSum(int s){
        sum = s; 

    //  t = (TextView)findViewById(R.id.textView1);
    //  t.setText(sum);
        System.out.println("In Set Sum"+s);
    }



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setup(); 
        t = (TextView)findViewById(R.id.textView1);

        ComponentName receiver = new ComponentName(getApplicationContext(), SampleBootReceiver.class);
        PackageManager pm = getApplicationContext().getPackageManager();

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


        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.set(Calendar.HOUR_OF_DAY, 17);
        calendar.set(Calendar.MINUTE, 05); // Particular minute
        calendar.set(Calendar.SECOND, 0);
        alarmMgr = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                1000*60*60*24, alarmIntent);
    }


    public void setup() {
        br = new BroadcastReceiver() {
            @Override
            public void onReceive(Context c, Intent i) {
                Toast.makeText(c, "Rise and Shine!", Toast.LENGTH_LONG).show();
                //Invoke the service here Put the wake lock and initiate bind service
                t.setText("Hello Alarm set");

                startService(new Intent(MainActivity.this, MyService.class));
                stopService(new Intent(MainActivity.this, MyService.class));

            }
        };
        registerReceiver(br, new IntentFilter("com.testrtc") );
        alarmIntent = PendingIntent.getBroadcast( this, 0, new Intent("com.testrtc"),0 );
        alarmMgr = (AlarmManager)(this.getSystemService( Context.ALARM_SERVICE ));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

This is my SampleBootReceiver Class inorder to check for reboots and set Alarms again after reboot, I am not sure if it works as intended. I could find no means to test if this is working properly but I do receive the Toast message about completion of boot.

public class SampleBootReceiver extends BroadcastReceiver {
    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    BroadcastReceiver br;
    TextView t; 
    MainActivity main; 

    @Override
    public void onReceive(Context context, Intent intent) {
        main= new MainActivity(); 
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            Toast.makeText(context, "Hello from Bootloader", 10000).show();
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(System.currentTimeMillis());
            calendar.set(Calendar.HOUR_OF_DAY, 15);
            calendar.set(Calendar.MINUTE, 50); // Particular minute
            calendar.set(Calendar.SECOND, 0);
            alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
            alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                    1000*60*60*24, alarmIntent);

            context.getApplicationContext().registerReceiver(br, new IntentFilter("com.testrtc") );
            alarmIntent = PendingIntent.getBroadcast( context.getApplicationContext(), 0, new Intent("com.testrtc"),
                    0 );
            alarmMgr = (AlarmManager)(context.getApplicationContext().getSystemService( Context.ALARM_SERVICE ));

        }
    }

}

The following is my service Class, unsure about the return I am doing here in the onStartCommand method:

public class MyService extends Service {

    int a = 2; 
    int b = 2; 
    int c = a+b; 


    public MainActivity main = new MainActivity();

    public MyService() {
    }



    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        Toast.makeText(this, "The new Service was Created", Toast.LENGTH_LONG).show();

    }

    @Override
    public int onStartCommand(Intent i, int flags , int startId){
        WakeLock wakeLock = null;

        try{
            PowerManager mgr = (PowerManager)getApplicationContext().getSystemService(Context.POWER_SERVICE);
            wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
            wakeLock.acquire();

            Toast.makeText(this, " Service Started", Toast.LENGTH_LONG).show();
             new Thread(new Runnable() { 
                    public void run(){


                        //Will be substituted with a time consuming long task. 

                        main.setSum(c); 

                    }
            }).start();

        }catch(Exception e){
            System.out.println(e);
        }finally{

            wakeLock.release();

        }

        return 1; 

    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();

    }
}

Also in the above, I want to know if the thread I am starting will interfare with how I am acquiring the wake lock. Also if I can use an async task and release the wakelock in onPostExecute?

Finally here is my Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.testrtc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.testrtc.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".SampleBootReceiver"
            android:enabled="false" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" >
                </action>
            </intent-filter>
        </receiver>

        <service
            android:name="com.testrtc.MyService"
            android:enabled="true"
            android:exported="true" >
        </service>

    </application>

</manifest>

THis is my log Cat after reboot, there are many log messages however I find these related to the app, they contain th epackage name:

01-22 15:18:35.652: V/ActivityManager(419): getTasks: max=1, flags=0, receiver=null
01-22 15:18:35.652: V/ActivityManager(419): com.xxx.xxx/.MainActivity: task=TaskRecord{425c58f0 #5 A com.xxx.xxx U 0}
01-22 15:18:35.653: V/ActivityManager(419): We have pending thumbnails: null

More Questions: Where should I set up the Alarm, if I do it in the onCreate of my Splash screen it will be called each time the app starts, maybe overwriting the older values.

Second I want to acquire a lock on the DB when my service is running, if in this time the user tries to open my app what do I do? (As data is getting updated I dont have anything to show).

Third in the above code I am still finding problems to register an alarm after reboot.

回答1:

1) To handle alarm at exact times you have 2 options: a) Set minimum SDK level at 18 and not 19. This will make your app work on kitkat at the exact time b) Use the setExact() method to tell Kitkat to keep your alarm at the exact time specified Source: http://developer.android.com/about/versions/android-4.4.html

2) The boot completed notification is the best way to go. You can set up a Broadcast receiver to get the notification on a reboot. You can store alarms in a Database and retrieve on reboot.

2.1) It is horrible programming practice to do something the user did not want you to do, i.e. set an alarm even though user has not opened the app.

3) As long as you don't create files on the SD card, all application data is removed upon uninstall of the app

4) You should lock/unlock data when writing or reading from it to solve your 12AM problem. If the data is locked, it would not get affected until the user has committed the transaction. If you are using SQLite you can get more information on this from: http://www.sqlite.org/lockingv3.html



回答2:

I think, you can write a Service something similar to the below one (This will do your task for every 5 minutes) and start the Service from the BroadcastReceiver which listens to BOOT_COMPLETED state.

public class YourService extends IntentService {

    public YourService() {
        super("YourService");
    }

    public YourService(String name) {
        super(name);
    }

    private static Timer timer = new Timer();

    private class mainTask extends TimerTask {
        public void run() {
            // TASK WHATEVER YOU WANT TO DO 
        }
    }

    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        timer.scheduleAtFixedRate(new mainTask(), 0, 300000);  // 5 minutes
    }
}