1

I am currently working on an app that uploads a file to my server. My app is designed to been started, used till task is done, and at the end the file upload will be initiated by pressing a button and the app has been used what it is there for. So a very linear approach: start, use till the end, file upload, done.

Now I would like to also send the file to my server if for some reason one decides not to use it till the end. So when the onStop() gets called.

After some research I decided to go with a unbound service that gets called in onStop() with startService(). Within that service there is while loop that is executed until the upload has been successful (serverResponseCode == 200) and sleeps after every unsuccessful attempt.

When the user starts using the stopped app again, thus calling onResume() I would like to cancel the file transfer as the user will upload the file at the end anyways by clicking the upload button in a different activty. So when I try to stop the service in onResume(), I receive the Toast that the service is stopped, but I see in my logcat that it is still running: try to connect to the server and sleeping.

What I have tried so far:

1) tried what was suggested on the the answers in:

automatically start up a service when the application is closed

stop service in android

2) Even when onStartCommand returns START_NOT_STICKY it happens.

HERE MY CODE:

Service Code:

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class UploadService extends Service {
    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;
    private static final String TAG = "UploadService";

    int serverResponseCode = 0;

    String upLoadServerUri = null;
    String uploadFilePath;
    String uploadFileName;

    // 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) {

           //... have some Server connection parameters here

            while (serverResponseCode != 200) {

                // ... trying to upload a file to my server

                if (serverResponseCode != 200) {
                    try {
                        Log.d(TAG, "Thread slepps");
                        Thread.sleep(10000);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            stopSelf(msg.arg1);
        }
    }


    private class DisplayToast implements Runnable {
        String mText;

        public DisplayToast(String text) {
            mText = text;
        }

        public void run() {
            Toast.makeText(UploadService.this, mText, Toast.LENGTH_SHORT).show();
        }
    }


    @Override
    public void onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);

        // If we get killed, after returning from here, restart
        return START_STICKY;
    }


    @Override
    public void onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}

Activity that calls starts and stops the service:

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.LayerDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.PopupMenu;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RatingBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import java.text.SimpleDateFormat;
import java.util.Date;

public class AppSelectionActivity extends AppCompatActivity implements View.OnClickListener, OnQueryTextListener {

    private final static String TAG = "AppSelectionActivity";
    private boolean leftOnIntent = false;  // is set to true when starting one of my other activity with an intent, so that service will not be started
    private Intent serviceIntent;

    @Override
    protected void onStop() {
        Log.d(TAG,"onStop: app is stopped");
        if (!leftOnIntent) {
            serviceIntent = new Intent(AppSelectionActivity.this, UploadService.class);
            startService(serviceIntent);
            Log.d(TAG, "onStop: Intent is started");
        }
        super.onStop();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"onResume: app is resumed");
        stopService(new Intent(AppSelectionActivity.this, UploadService.class));
        leftOnIntent = false;
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG,"onDestroy: got called");
        super.onDestroy();
    }
}

Any suggestions how to stop the service and the file uploade correctly?

Community
  • 1
  • 1
Savate
  • 15
  • 4

1 Answers1

0

Your Handler needs some flag which you can set to stop the upload loop (while (serverResponseCode != 200) ). So let's introduce a

public boolean keepRunning;

Modify the loop as follows:

keepRunning = true;
while (serverResponseCode != 200 && keepRunning)
{
      // ... trying to upload a file to my server
      if (serverResponseCode != 200) {
          try {
              Log.d(TAG, "Thread slepps");
              Thread.sleep(10000);

          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
}

If you want to stop your Service, you can now do so by calling startService() with a certain Intent action ...

Intent intent = new Intent(this, UploadService.class);
intent.setAction("your.package.name.STOP_UPLOAD");
startService(intent);

... which you then evaluate in onStartCommand().

String intentAction = (intent != null) ? intent.getAction() : "";
if ( "your.package.name.STOP_UPLOAD".equals(intentAction) )
{
    mServiceHandler.keepRunning = false;
    stopSelf();
}
else
{
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    mServiceHandler.sendMessage(msg);
}
return START_STICKY;

There are some methods involved here which are called by the runtime if certain other methods in your code are executed:

  • Service.onStartCommand() is triggered by startService()
  • Handler.handleMessage() is triggered by sendMessage();

In your case, handleMessage() is called by the runtime after you call mServiceHandler.sendMessage(msg);. This will start the upload which may still be busy when the user returns to the Activity and so triggers onResume(). So in this method, you call startService() to abort the upload.

This triggers onStartCommand(). After evaluating the Intent action, the boolean will be set to false. The Handler may still be executing the while loop. If this is the case, the loop will stop now because the condition is no longer true.

Bö macht Blau
  • 12,820
  • 5
  • 40
  • 61
  • many thanks your answer helped alot and works like a charm. I only had to add 'return START_NOT_STICKY;' after stopSelf(); in the if statement otherwise the service starts again even when activity is seen. – Savate May 10 '16 at 17:56
  • One more thing, may I ask if you could elaborate when onStartCommand and handleMessage are called? It seem that the boolean keepRunning is set to false before handleMessage() is started, so that the upload wont even start, but keepRunning is set to true right before the while loop. So my logic says it should be overwritten to true. But It works anyways, how come? Because the when the app is started the service is started too and the service sets up a connection to the server as well, but the fileupload does not happen. Could you tell me if it is just luck? Much appreciated – Savate May 10 '16 at 18:04
  • found a bug: if I start the app, then leave the via onStop() [e.g. go to homescreen] then manually stop the service via settings -> applications -> running, the thread wont be killed (probably because of START_STICKY but when I then resume to my app/activity your solution will not and the thread will continue to sleep and wake up regardless. This case might be a little unlikely but just curious. Any ideas why that is? – Savate May 10 '16 at 18:43
  • @Savate - thanks for your feedback. Please take a look at my edited answer. About "manually" stopping the app - well, users are warned: "if you stop an app, it may misbehave". I would have expected *the whole process to be stopped*, including the Handler, even if it is running on another Thread. Slow garbage collector? The point is, after a FORCE STOP, you don't simply return. Every instance of Activity, Service, Handler etc. will be created from scratch.The new mServiceHandler object is not identical to the previous one. The old Handler is unreachable by now. Its flag will always stay "true". – Bö macht Blau May 11 '16 at 07:03
  • @Savate - (cont.) I must confess I'm a little out of my depth here. Possible workaround: let the Handler have a counter and a (very large but finite ) limit for loop repetitions so it won't run for the rest of the device lifetime. More complicated workaround: consider using an IntentService which runs on its own Thread but has / is a kind of Context, so you could communicate (set Flags) by writing to SharedPreferences. – Bö macht Blau May 11 '16 at 07:15