Error when using the sendMessage () method of the Handler class. "The message has not been posted or has already been removed"

3

I'm studying about Threads and Services and I know that to run a longer process, like searching internet signal or download, I must put it to run on a Background Thread and not on the UI (User Interface) Thread. In the app below I made an example of code running in the background and as this process progresses, there are updates in the progress bar, but when I run the application, I get an error saying the following:

  

java.lang.IllegalStateException: The specified message queue synchronization barrier token has not been posted or has already been removed.

Below is the code:

package com.gabrielm.myapplication;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;

public class MainActivity extends AppCompatActivity {

    Handler mHandler;
    Button mButton;
    ProgressBar mProgressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
        mButton = (Button) findViewById(R.id.button);

        mHandler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                mProgressBar.setProgress(msg.arg1);
            }
        };

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        Message msg = Message.obtain();
                        for (int i = 0; i < 100; i++) {

                            msg.arg1 = i;
                            mHandler.sendMessage(msg);

                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
            }
        });
    }
}
Searching the internet for a possible solution to the problem, I found that the sendMessage(msg) method has to always send a new object of type Message to the message queue every time I want to send a new message. That is, I can not reuse the same object, I have to always create a new one every time I want to send a data to handleMessage(Message msg); . So what I did was remove the code line Message msg = Message.obtains(); from the place where it was and place it inside the for block, because so every time for executes, it creates a new object of type Message .

This little change in code made the program work, but I'm not sure if what I did was the most correct way to do the process. So I wonder if the logic I've developed here is right.

    
asked by anonymous 27.04.2016 / 05:39

2 answers

2

Running background codes on Android using Handlers and Threads is often a complicated and error-prone task, so a special class has been created to make it easy to perform asynchronous tasks: a Async Task .

AsynTask provides the following methods:

  • onPreExecute : Method called before the long-running task runs. It is usually used to prepare and start showing a progress bar (still empty).

  • doInBackground : Performs a long-running task (DB access, document download, etc.).

    Attention: Within this method, no access to elements of the view should be made. If this is done, you will get an error of type java.lang. RuntimeException: Can not create handler inside thread that has not called Looper.prepare () "

    If you want to manipulate the view, you should use the methods onPreExecute() and onPostExecute()

    However, if you really need to access the view within the doInBackground() method, put the view access method inside the runOnUiThread() method and place it within doInBackground() :

    @Override
    protected String doInBackground(String... parametros) {
    
        // Realiza tarefa de longa duração ...
    
        // Acessa a view
        runOnUiThread(new Runnable() {
    
            public void run() {
    
                // faça o acesso a view aqui
            }
        });
    } 
    
  • onProgressUpdate : Used to update the status of the long-running task ( e.g. increment a progress bar). This method is called every time a call to the publishProgress()
  • method is called.
  • onPostExecute : Called once the long-running task is completed. It is used to access and handle the return variable of the long-running task

  • It is important to know that since AsyncTask is an abstract class, it can only be extended, not instantiated, as well, because it has generic parameters in its constructor ( AsyncTask <Params, Progress, Result> ). extend the AsyncTask class you should provide the type of these generic parameters, where:

    • Params : This is the input parameter type of method doInBackground()
    • Progress : Is the input parameter type of method onProgressUpdate()
    • Result : Is the input parameter type of method onPostExecute()

    So, to create an AsyncTask that downloads a file, for example, you could do:

    // A classe vai receber um conjuntos de URLs como entrada,
    // vai enviar um Integer a cada chamada ao método publishProgress()
    // e vai enviar um Integer quando a tarefa for finalizada
    private class DownloadArquivos extends AsyncTask<URL, Integer, Integer> {
    
        protected Long doInBackground(URL... urls) {
    
         // numero de downloads executados
         int downloads = 0;
    
         for (int i = 0; i < urls.lenght ; i++) {
    
             downloads += downloadFile(urls[i]);
             // downloadFile() é o seu método de longa duração
    
             // Atualiza o progresso enviando a porcentagem de arquivos
             // baixados até o momento
             publishProgress( (int) ((i / (float) urls.lenght) * 100) );
         }
    
         return downloads;
     }
    
     protected void onProgressUpdate(Integer... progress) {
    
          // método da sua view que atualiza o progress bar
          setProgressPercent(progress[0]);
     }
    
     protected void onPostExecute(Int result) {
    
         // método da sua view que mostra uma mensagem
        // após todos os downloads serem efetuados
         showDialog( result + " arquivos baixados com sucesso!" );
     }
     }
    

    Note: Code removed and adapted from: link

        
    27.04.2016 / 15:55
    2

    Gabriel, your research has brought you correct information. It is not possible to send the same Message object several times, so we always have to create a new instance. Rather, instead of creating an instance using new Message() , we can use Message.obtain() which will return an instance of Message coming from a pool of recycled objects, which is less expensive than the first option.

      

    While the constructor of Message is public, the best way to get one of   these are to call Message.obtain () or one of the   Handler.obtainMessage () methods, which will pull them from a pool of   recycled objects.

        
    27.04.2016 / 15:15