This probably shouldn't be too hard to make but I was wondering what would be the best practice in doing it. I plan to create something like this:
Service needs to be running all the time. It also needs to start when user boot his device.
Service is processing data from database so it needs to listen for database changes for one table.
After user makes an action to insert new row to database, service should register that and start processing data.
I am not sure how to listen for those changes and process them. I know I can start a service on boot by creating new Broadcats receiver:
public class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MyService.class);
context.startService(service);
}
}
And defining it inside manifest:
<receiver android:name="MyReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
And this is simple service:
public class MyService extends Service {
private final IBinder mBinder = new MyBinder();
private ArrayList<String> list = new ArrayList<String>();
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Should check for database change here?
// fetches data from database
List<String> data = Manager.get(context).getData();
return Service.START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
}
Service should be running now. How can I now implement periodic check of my database or listen for database changes? What would the recommended design or best practice to implement this process? Any idea is most welcome :)
EDIT
Some extra explanation. User who is using the app is the only one who can update database. It's a local SQLite database which is created inside a class which extends SQLiteOpenHelper. The database is updated when user fills some predefined EditText views inside Activity and clicks on a button to save data. Service needs to process each new row inside that table. Sometimes it can happen the user turns off his device or kills his app before data is processed. In that case service needs to continue processing data after user boots his device again or starts an app.
If your app is the only one that can modify the database, and only as a result of user action, then you always know exactly the moment your table has been updated. Simply initiate the processing whenever you perform an insert.
If you want that processing done in a service, use an IntentService. IntentService processes everything on a background thread so you can do operations like network requests, and it automatically stops itself when it has no more work to do.
I don't know how long this processing of yours takes, but I don't think you should be fearful of the app being killed or the device being turned off. If the user closes the app, the system will not kill it immediately; if the OS does need to kill one of your application components to regain memory, it is less likely to kill your IntentService than, say, the Activities that are no longer being used (and by then you might be done with your processing anyway, so it won't matter). But in general if the OS is not under memory pressure then it will keep app components around as long as possible so that it takes less time if the user switches back to that app.
The only things that would actually kill your app is a device with too much memory pressure or the user is using a task killer app. If that's truly a concern for you (or if your processing takes so long that you think those situations could happen), then you can consider persisting pending processing tasks to disk before you begin processing. When a task finishes, remove it from disk. Then your boot completed receiver only needs to check if there are pending tasks that didn't complete. You can build your own solution, or you can look at something like Path's JobQueue library: https://github.com/path/android-priority-jobqueue
You could make your service a Singleton and create another BroadcastReceiver just for database changes:
public class YourService extends Service{
private static YourService sInstance;
public static YourService getInstance(){ return sInstance; }
onCreate(){
sInstance = this;
...
}
public void processSomeData(YourData data){
//do the stuff you wanna do
}
}
And your new receiver could look like this
public class DBChangeReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
YourData data;
//grab your data from the intent
YourService.getInstance().processSomeData(data);
}
}
Then just call sendBroadcast() when you update your database
Related
public class DataManager extends IntentService {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
}
public DataManager() {
super("DataManager");
setIntentRedelivery(true);
}
#Override
protected void onHandleIntent(final Intent intent) {
// download and parsing task done here
}
}
This is my intent service which i am using to download file and parse it. Now if i get a new request for a file download, i have to clear the ongoing task and start the download for new request cancelling the older one. so i use the below code for doing it :.
private void refreshSync() {
context.stopService(new Intent(context, DataManager.class));
final Intent mServiceIntent = new Intent(context, DataManager.class);
mServiceIntent.putExtras(bundle);
context.startService(mServiceIntent);
}
So the service gets killed and the next request to start service is intented. But the previous tasks starts again running two parallel tasks performing download. Basically the previous task doesnt get killed which i intended to.
Is there any work around to kill the ongoing task of the service and start another fresh task ?
Don't use IntentService. This doesn't match your requirements. IntentService is a simple Service that accepts a queue of work and processes the queue and then shuts itself down when the queue is empty.
You need more intelligence, and you are better off implementing that yourself. Just extend Service instead of IntentService. In onStartCommand() start a background Thread that downloads the data. Keep track of that background Thread in a member variable in the Service. If startService() gets called again, check if you already have a download in progress. If so, stop it and start a new background Thread to download the new file. To stop a background thread, you should provide a boolean variable in the Thread that gets examined every now and then inside the download loop. If that variable's state changes, it means the Thread should stop. This is a standard mechanism for stopping background threads in Java.
You are setting setIntentRedelivery(true);, that force the intents to survive calls of the service if they are not handled completely (if onHandleIntent doesn't manage to return). Taking into account the fact that IntentService has only one working thread (can execute only one task at a time) the behavior of the service completely depends on the onHandleIntent implementation. So you need either analyze implementation and change it according to you goals, or set setIntentRedelivery(false);
I'm working on an application that uses Android's DownloadManager to download files. In my AndroidManifest.xml I register a receiver to listen for downloads completing like this
<receiver android:name=".download.DownloadCompleteBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
In the broadcast receiver's onReceive method, I launch an IntentService to perform some post-processing on the downloaded file before it's ready for use. Here's what that code looks like:
public class DownloadCompleteBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
try {
final long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
context.startService(
new Intent(context, DownloadService.class)
.putExtra("downloadId", downloadId));
} catch (Throwable t) {
Log.i("Yo", Log.getStackTraceString(t));
}
}
The DownloadService class looks something like this
public class DownloadService extends IntentService {
#Override
protected void onHandleIntent(Intent intent) {
// Some processing logic
...
// Send processing complete
sendBroadcast(new Intent("com.example.package.DownloadService:PROCESSING_COMPLETE"));
}
}
This service is registered in my AndroidManifest.xml like this
<service android:name=".download.DownloadService" android:process=":Downloads"/>
Finally, I have an activity that listens for that PROCESSING_COMPLETE broadcast. That code looks like this
public class MyActivity extends AppCompatActivity {
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Update some UI
}
}
#Override
protected void onResume() {
super.onResume();
registerReceiver(receiver, new IntentFilter("com.example.package.DownloadService:PROCESSING_COMPLETE"));
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
}
And finally, the issues:
1) I am seeing delays (as much as 20 seconds) between when the download manager completes a download and when my registered BroadcastReceiver receives the download complete notifications. Is this normal? This issue/question is not as big of a concern as the next two.
2) I am seeing delays (as much as 2 minutes) between when my BroadcastReceiver calls startService() and when DownloadService.onCreate() is called. What's going on here? Sometimes my devices will show an ANR dialog asking me to wait for the app or force close it. I know I declared the service to run in a separate process, and there's some lead time to create the process and launch the service, however I'm seeing similar delays in successive runs when the process is already created. Moving DownloadService to the default process doesn't appear to improve the load time, however I believe the best practice is to run such a service in it's own process to keep it from being terminated should the app crash.
3) I am seeing delays (as much as 2 minutes) between when the DownloadService sends the PROCESSING_COMPETE broadcast and when my activity actually receives it. This is without leaving the activity then coming back (an onPause() and onResume()cycle), though my code handles unregistering and re-registering. What's going on here? I'm testing on a Galaxy S7 running 6.0. I hardly have any apps installed/running on this device that I could image would be slowing down broadcasts. Another interesting observation is that if I send multiple broadcasts, they're all delivered at the same time after the long delay.
Thanks for the help in advance!
What is the recommended approach for checking for new data regardless if the app is in the foreground or background? I am wondering which Android API people are typically using to do this. There seems to be a few ways to achieve my goal, and I want to make sure I'm on the right path.
I have something put together which uses AlarmManager.SetInexactRepeating() to call an IntentService which does the sync and inserts/updates data in the database. This works while the app is in the foreground and background, but if I force stop the app then I keep seeing "Unfortunately, has stopped working" messages when the AlarmManager alarm would've triggered. In this case, I only care about checking for new data only when the app is running in the foreground or background.
My first thought is to detect when the app is force closed, and stop the alarm, but that does not seem possible. So I am asking here, is my approach wrong? If so, which approach is used to perform some periodic task regardless if the phone is in the foreground or background? The problem with the AlarmManager solution I am using is the alarms continue to fire even when the app is closed.
If your idea is to check if your API has new data and perform a background sync to your local database or other data storage, I think you would like to take a look at this:
Creating a Sync Adapter
Running a Sync Adapter
The Sync adapter is the recommended way of achieving this in Android. The pros of using it are multiple:
Optimisations out of the box - the OS bundles calls, uses the most appropriate windows to run the sync adapter at a minimal bandwidth and battery cost
The lifecycle of your background sync component is managed internally by the OS
Observers can be notified when data has been changed so the UI can be updated easily
Multiple ways of running the sync - at intervals, automatically with the OS message to keep TCP/IP connections open or on demand
However, implementing this requires some things, that can cause a bit of a pain at first:
It is mandatory that the adapter works with a ContentProvider
Sync Adapters use Account for authentication. If this is not needed, a Stub has to be provided
For backgrounding on Android usually you use even a Service that can run alone and independently from the App or a Bounded service that takes and returns data from the App. A complete reference on backgrounding can be found here
Using a Service is the right way to go. Have your app start the Service and it will continue running while the app is in the foreground or the background. Then, if you want to kill the Service when your app closes, you could just call stopService(yourServiceIntent); from the onDestroy() override in your app's activity. That should effectively shut down the service when the app closes.
So some sample code of how this works (taken from the Services docs)...
The Service (just Logs a message every 1 second for 60 seconds):
public class MyService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
long endTime = System.currentTimeMillis() + 60*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(1000);
Log.d("SERVICE", "The service is still running.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
stopSelf(msg.arg1);
}
}
#Override
public void onCreate() {
HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
return START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
}
}
And in your activity you would do something like:
public class MainActivity extends AppCompatActivity {
Intent serviceIntent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceIntent = new Intent(this, MyService.class);
startService(serviceIntent);
}
#Override
protected void onDestroy() {
stopService(serviceIntent);
super.onDestroy();
}
I have an intentservice that gets qued by the user and by my app automatically. I need to be able to kill all pending intents that are qued when the user logs out of my application, but I cannot seem to get that to work. I have tried stopService() and stopself(), but the intents continue to fire off the intentservice after the user has logged out. I would try to get the id of the intent but that is difficult as everytime the intentservice starts, the variable holding the intent id's is empty. Here is my intentservice code:
public class MainUploadIntentService extends IntentService {
private final String TAG = "MAINUPLOADINTSER";
private GMLHandsetApplication app = null;
private SimpleDateFormat sdf = null;
public boolean recStops = true;
public MainUploadIntentService() {
super("Main Upload Intent Service");
GMLHandsetApplication.writeToLogs(TAG,
"GMLMainUploadIntentService Constructor");
}
#Override
protected void onHandleIntent(Intent intent) {
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Started");
if (app == null) {
app = (GMLHandsetApplication) getApplication();
}
uploadData(app);
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Finished");
}
#Override
public void onDestroy() {
GMLHandsetApplication.writeToLogs(TAG, "onDestroy Started");
app = null;
stopSelf();
GMLHandsetApplication.writeToLogs(TAG, "onDestroy completed");
}
public void uploadData(GMLHandsetApplication appl) {
//All of my code that needs to be ran
}
Unfortunately, I don't think it's possible to accomplish that with the standard IntentService methods since it doesn't offer a way to interrupt it while it's already going.
There are a few options I can think of that you can try to see if they fit your need.
Copy the IntentService code to make your own modifications to it that would allow you to remove pending messages. Looks like someone had some success with that here: Android: intentservice, how abort or skip a task in the handleintent queue
Instead of copying all the IntentService code, you might also be able to Bind to it like a normal Service (since IntentService extends Service) so you can write your own function to remove pending messages. This one is also mentioned in that link.
Rewrite the IntentService as a regular Service instead. With this option, you'd have more control over adding and removing messages.
I had what sounds like a similar situation where I was using an IntentService, and I eventually just converted it to a Service instead. That let me run the tasks concurrently and also cancel them when I needed to clear them.
Here
When should I free the native (Android NDK) handles? is the HangAroundIntentService class that has the method cancelQueue().
The class also has the method
public static Intent markedAsCancelIntent(Intent intent)
that converts an intent into a cancel intent, and
public static boolean isCancelIntent(Intent intent).
The class is based on the open-sourced Google's code.
Just a thought but inside of your onhandleintent can you have an argument that checks to see if app is running if not then don't run the code? example. In the start of your app you could have a static var
boolean appRunning;
Next in your onhandle of the intent, when you set the appRunning to false, after an onPause or onDestroy of activity, you could wrap the onhandleintent code in a boolean:
protected void onHandleIntent(final Intent intent) {
if(MainActivity.appRunning){
...
}
}
Just a thought
I make login in my main activity.
If the login is correct, I wanna see my profile and download some data from server, but I cannot keep the connection, if I change activity.
How can I do this?
Once you verify the login is correct (by testing the connection to the server and
authenticating), you can store the login details in SharedPreferences or something similar.
Then you can just make subsequent requests using those login details (no matter which activity you are in). This is, of course, assuming the server accepts authentication this way.
You could also use a Service. Services are long running background tasks that live through Activities.
In your login form you could send a startService intent similar to starting a new activity. This calls onStartCommand on the Service class where you can create the connection and store it in the Service object itself. When you require information from the connection, you can acquire the service instance using bindService call.
Below is an example of a basic login service adapted from Google's basic example.
public class LocalService extends Service {
private MyConnection conn;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent i = new Intent("my.service.connected");
try {
conn = new MyConnection(
intent.getStringExtra("username"),
intent.getStringExtra("password"));
i.putExtra("succeeded", true);
sendBroadcast(i);
} catch (ConnectionException ex) {
i.putExtra("succeeded", false);
sendBroadcast(i);
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
}
The Android.Manifest.xml also needs <service android:name="LocalService"></service> element inside the <application />.
Services don't come without their downsides though. Generally you should pass the startup parameters in an Intent which means they need to be serializable or parcelable. This prevents you from creating the connection in your Activity, checking that login succeeded and sending it the service.* Instead you need to send the login parameters to the service and perform the login there.
The complications come when you need to resolve whether the connection succeeded and you should proceed to the next activity or if it failed and you should show an error message. There are several solutions, but all of them require some setup. Especially when you consider that the user may pause your activity between pressing 'Login' and actually logging in.
As shown above, one option is to notify the activity by sending broadcasts from the Service. Below is an activity that launches the above service and listens to the broadcasts. The below activity does not take into account the possibility that the activity may be paused at any moment.
public class LoginActivity extends Activity {
class MyReceiver extends BroadcastReceiver {
#Override
public final void onReceive(Context ctx, Intent i) {
if (i.getBooleanExtra("succeeded", false)) {
startMyMainActivity();
} else {
showErrorMessage();
}
}
}
private BroadcastReceiver bcReceiver;
private void doLogin(String username, String password) {
// Register receiver that listens for connected messages.
bcReceiver = new MyReceiver();
IntentFilter ifilter = new IntentFilter("my.service.connected");
registerReceiver(bcReceiver, ifilter);
// Send command to start service (connects)
Intent i = new Intent(this, LocalService.class);
i.putExtra("username", username);
i.putExtra("password", password);
startService(i);
}
#Override
protected void onPause() {
if (bcReceiver != null) unregisterReceiver(bcReceiver);
super.onPause();
}
}
When the Activity should handle paused activity gracefully you should keep track of its state in a shared variable. If it was previously in a state where it tracked the Service it should bind to the Service and check its state when resuming. If the service is still connecting (as opposed to connected or disconnected) it should re-register the broadcast listener.
I wonder why you need to keep the connection. Maybe you can just reopen the connection and login again?
But if you really need to keep the connection open: You could can keep the connection in the Application. But make sure you close the connection when the user goes away from your app! I really don't recommend this. Or set a timeout for the connection to be closed like after 5 minutes of inactivity.
http://developer.android.com/reference/android/app/Application.html
You will have to say in the AndroidManifest which Application your App uses.
http://developer.android.com/guide/topics/manifest/application-element.html#nm (see andriod:name).
Then you can get the application from the activity through: getApplication(). Don't forget to cast it. Than add functions where you can login and so on.
Edit:
A little more detailed:
You will have to create a class extending Application. The name of this class you enter in the android:name of the application in the manifest. In that class you write functions with which you handle the connection. Like: startConnection() doLogin() stopConnection() ... In the Activities you call:
NameOfYouApplication app = (NameOfYouApplication) getApplication();
app.startConnection();
app.doLogin();
kind of like that. But beware that even if the Activity is closed your Application will stay alive. You better find a good time to close the connection.