Why Future thread doesn't work in background o

2019-07-27 02:01发布

Today I found a wierd problem. What I want is to check server availability (Particularly SSL checking) once application started and display proper message if server is down. This process should work in background and user is able to navigate the app if server has problem (app can works offline).

What I did is simple. In main activity I have

@Override
protected void onStart()
{
    super.onStart();

    // Check Internet connection

    // Check Location sensor

    // Check server accessibility
    BackendCheck backendCheck = new BackendCheck(this);
    if (!backendCheck.execute())
    {
        displayErrorDialog();
        return;
    }
}

This is BackendCheck class:

    public class BackendCheck implements Callable<Boolean>
    {
        private static final String TAG = BackendCheck.class.getSimpleName();

        // Thread sleep time
        private static final int THREAD_SLEEP = 5000;

        // Number of attempts to call an API in order to get response
        private static final int MAX_ATTEMPT = 3;

        // Current attempt
        private int counter = 0;

        // The url that should be used in order to get server response
        private String mTestUrl;

        // App mContext
        private Context mContext;

        // Server status
        private boolean mServerStatus = false;


        public BackendCheck(Context context)
        {
            this(context, "");
        }

        public BackendCheck(Context context, String url)
        {
            this.mTestUrl = url;
            this.mContext = context;
        }

        public boolean execute()
        {
            // Check #mTestUrl and use Feature API if this variable is empty
            if (TextUtils.isEmpty(mTestUrl))
            {
                mTestUrl = PassengerConstants.URL_BASE + mContext.getResources()
                        .getString(R.string.uri_feature_payments);
            }

            // Get ExecutorService from Executors utility class, thread pool size is 10
            ExecutorService executor = Executors.newFixedThreadPool(10);

            do
            {
                // Increment counter
                counter++;

                // Submit Callable tasks to be executed by thread pool
                Future<Boolean> future = executor.submit(this);

                try
                {
                    // Break Do-While loop if server responded to request (there is no error)
                    if (!future.get())
                    {
                        mServerStatus = true;
                        break;
                    }
                }
                catch (InterruptedException e)
                {
                    Logger.error(TAG, e.getMessage());
                }
                catch (ExecutionException e)
                {
                    Logger.error(TAG, e.getMessage());
                }

            } while (counter < MAX_ATTEMPT);

            // Shut down the executor service now
            executor.shutdown();

            // Return server status
            return mServerStatus;
        }

        @Override
        public Boolean call() throws Exception
        {
            // Sleep thread for a few seconds
            Thread.sleep(THREAD_SLEEP);

            try
            {
                HttpClient client = new DefaultHttpClient();
                HttpGet get = new HttpGet(mTestUrl);
                Logger.debug(TAG, "Attempt (" + counter + "), try to check => " + mTestUrl);

                HttpResponse httpResponse = client.execute(get);
                int connectionStatusCode = httpResponse.getStatusLine().getStatusCode();


           Logger.debug(TAG,
                    "Connection code: " + connectionStatusCode + " for Attempt (" + counter
                            + ") of request: " + mTestUrl);

            if (isServerError(connectionStatusCode))
            {
                return true;
            }
        }
        catch (IllegalArgumentException e)
        {
            Logger.error(TAG, e.getMessage());
        }
        catch (Exception e)
        {
            Logger.error(TAG, e.getMessage());
        }

        return false;
    }

    /**
     * Server status checker.
     *
     * @param statusCode status code of HTTP request
     * @return True if connection code is 5xx, False otherwise.
     */
    private static boolean isServerError(int statusCode)
    {
        return (statusCode >= HttpURLConnection.HTTP_INTERNAL_ERROR);
    }
}

What happens is, When I launch the application splash screen displays. Then after a few seconds mainActivity runs (first code) then - since my server is down (for testing purposes) - I have black screen for 15 seconds (since I set MAX_ATTEMPT to 3 and have 5 seconds thread sleep) and after that I'm able to see UI of mainActivity and my error message.

I expect Callable<> should works in background and I see mainActivity after splashScreen without problem (black screen).

What you think? What problem might be? Thanks.

2条回答
Ridiculous、
2楼-- · 2019-07-27 02:28

It would appear that you are executing the BackendCheck callable in the main thread.

Classes that extend Callable are usually executed via an ExecutorService which is a separate thread itself, thus it executes in the background. You may want to take a look at the Runnable interface or Thread if you'd like to run a separate thread to execute in the background that does not return a value. Calling the start method will cause the class to execute in a separate thread as indicated by the documentation:

When an object implementing interface Runnable is used to create a thread, starting the thread causes the object's run method to be called in that separately executing thread.

If you need to return some data at the end of execution I highly recommend an ExecutorService but you could probably also get away with using a FutureTask though I have less experience with that class. Hopefully that helps.

查看更多
萌系小妹纸
3楼-- · 2019-07-27 02:39

Okay, I just fixed my problem. 'njzk2' is right. The problem is future.get() which is running on or blocking main thread. I fixed the issue by doing a few changes.

  1. First, I call my execute() method from a new thread. Therefore the whole of processing will be done in another thread.
  2. I added new start() method in order to run it.
  3. Add a listener in BackendCheck class and implemented it in my activity.
  4. Since I want to display a dialog if server is down and I'm in another thread then runOnUiThread(runnable) uses to show the dialog in main thread.

This is my complete code for your reference. In my activity:

@Override
    protected void onStart()
    {
        super.onStart();

        // Check Location sensor

        // Check server accessibility
        BackendCheck backendCheck = new BackendCheck(this);
        backendCheck.setServerListener(new BackendCheck.BackendCheckListener()
        {
            @Override
            public void onServerIsDown()
            {
                MainActivity.this.runOnUiThread(new Runnable() {
                    public void run() {
                        displayErrorDialog();
                    }
                });
            }
        });
        backendCheck.start();
    }

And my BackendCheck class:

public class BackendCheck implements Callable<Boolean>
{
    public interface BackendCheckListener
    {
        public void onServerIsDown();
    }

    private static final String TAG = BackendCheck.class.getSimpleName();

    // Thread sleep time
    private static final int THREAD_SLEEP = 5000;

    // Number of attempts to call an API in order to get response
    private static final int MAX_ATTEMPT = 3;

    // Current attempt
    private int counter = 0;

    // The url that should be used in order to get server response
    private String mTestUrl;

    // App mContext
    private Context mContext;

    // Server status
    private boolean mIsServerWorking = false;

    // Server listener
    private BackendCheckListener mListener;


    public BackendCheck(Context context)
    {
        this(context, "");
    }

    public BackendCheck(Context context, String url)
    {
        this.mTestUrl = url;
        this.mContext = context;
    }

    public void setServerListener (BackendCheckListener listener)
    {
        this.mListener = listener;
    }

    public void start()
    {
        Thread thread = new Thread()
        {
            @Override
            public void run() {
                boolean isServerWorking = execute();
                if(!isServerWorking)
                {
                    mListener.onServerIsDown();
                }
            }
        };

        thread.start();
    }

    private boolean execute()
    {
        // Check #mTestUrl and use Feature API if this variable is empty
        if (TextUtils.isEmpty(mTestUrl))
        {
            mTestUrl = PassengerConstants.URL_BASE + mContext.getResources()
                    .getString(R.string.uri_feature_payments);
        }

        // Get ExecutorService from Executors utility class
        ExecutorService executor = Executors.newFixedThreadPool(1);

        do
        {
            // Increment counter
            counter++;

            // Submit Callable tasks to be executed by thread pool
            Future<Boolean> future = executor.submit(this);

            try
            {
                // Skip sleeping in first attempt
               if(counter > 1)
                {
                    // Sleep thread for a few seconds
                    Thread.sleep(THREAD_SLEEP);
                }

                // Break Do-While loop if server responded to request (there is no error)
                if (!future.get())
                {
                    mIsServerWorking = true;
                    break;
                }
            }
            catch (InterruptedException e)
            {
                Logger.error(TAG, e.getMessage());
            }
            catch (ExecutionException e)
            {
                Logger.error(TAG, e.getMessage());
            }

        } while (counter < MAX_ATTEMPT);

        // Try to shut down the executor service now
        try
        {
            executor.shutdown();
            executor.awaitTermination(THREAD_SLEEP, TimeUnit.MILLISECONDS);
        }
        catch (InterruptedException e)
        {
            Logger.error(TAG, e.getMessage());
        }

        // Return server status
        return mIsServerWorking;
    }

    @Override
    public Boolean call() throws Exception
    {
        try
        {
            HttpClient client = new DefaultHttpClient();
            HttpGet get = new HttpGet(mTestUrl);
            Logger.debug(TAG, "Attempt (" + counter + "), try to check => " + mTestUrl);

            HttpResponse httpResponse = client.execute(get);
            int connectionStatusCode = httpResponse.getStatusLine().getStatusCode();
            Logger.debug(TAG,
                    "Connection code: " + connectionStatusCode + " for Attempt (" + counter
                            + ") of request: " + mTestUrl);

            if (isServerError(connectionStatusCode))
            {
                return true;
            }
        }
        catch (IllegalArgumentException e)
        {
            Logger.error(TAG, e.getMessage());
        }
        catch (Exception e)
        {
            Logger.error(TAG, e.getMessage());
        }

        return false;
    }

    /**
     * Server status checker.
     *
     * @param statusCode status code of HTTP request
     * @return True if connection code is 5xx, False otherwise.
     */
    private static boolean isServerError(int statusCode)
    {
        return (statusCode >= HttpURLConnection.HTTP_INTERNAL_ERROR);
    }
}
查看更多
登录 后发表回答