How to return calculations made in Service for activity? [duplicate]

1

I would like to send the result of a calculation back to the activity, I was trying to use a Binder as I was advised, but I can not retrieve this value. Would you like to make the activity learn that the service has finished calculating and then get the value?

My activity:

public class MainActivity extends ActionBarActivity  implements ServiceConnection{

private TextView numero1;
private TextView resultado;
private TextView numero2;
private TextView resultadosoma;
private EditText numero1informado;
private EditText numero2informado;
private Valores valores;
final ServiceConnection conexao = this;
final Intent it = new Intent("Service");

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

    Button biniciar = (Button) findViewById(R.id.button1);
    Button bfechar = (Button) findViewById(R.id.button2);
    numero1 = (TextView) findViewById(R.id.numero1);
    numero2 = (TextView) findViewById(R.id.numero2);
    resultado = (TextView) findViewById(R.id.resultado);
    resultadosoma = (TextView) findViewById(R.id.resultadosoma);
    numero1informado = (EditText) findViewById(R.id.numero1informado);
    numero2informado = (EditText) findViewById(R.id.numero2informado);





    biniciar.setOnClickListener(new Button.OnClickListener(){
        public void onClick(View v){
            it.putExtra("numero1", numero1informado.getText().toString());
            it.putExtra("numero2", numero2informado.getText().toString());

            //startService(it);
            Class<Service> classeServico = Service.class;
            bindService(new Intent(MainActivity.this, classeServico), conexao, Context.BIND_AUTO_CREATE);
            //classeServico.get
            startService(it);


        }
    });



    bfechar.setOnClickListener(new Button.OnClickListener(){
        public void onClick(View v){
            //stopService(it);
            unbindService(conexao);
            stopService(it);
        }
    });
}


@Override
protected void onDestroy() {

    unbindService(conexao);
    super.onDestroy();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    // TODO Auto-generated method stub
    LocalBinder binder = (LocalBinder) service;

    valores = binder.getValores();

}

@Override
public void onServiceDisconnected(ComponentName name) {
    // TODO Auto-generated method stub
    valores = null;
}


@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;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}

public TextView getNumero1() {
    return numero1;
}

public void setNumero1(TextView numero1) {
    this.numero1 = numero1;
}

public TextView getResultado() {
    return resultado;
}

public void setResultado(TextView resultado) {
    this.resultado = resultado;
}

public TextView getNumero2() {
    return numero2;
}

public void setNumero2(TextView numero2) {
    this.numero2 = numero2;
}

public TextView getResultadosoma() {
    return resultadosoma;
}

public void setResultadosoma(TextView resultadosoma) {
    this.resultadosoma = resultadosoma;
}

public EditText getNumero1informado() {
    return numero1informado;
}

public void setNumero1informado(EditText numero1informado) {
    this.numero1informado = numero1informado;
}

public EditText getNumero2informado() {
    return numero2informado;
}

public void setNumero2informado(EditText numero2informado) {
    this.numero2informado = numero2informado;
}

public Valores getValores() {
    return valores;
}

public void setValores(Valores valores) {
    this.valores = valores;
}

}

Service:

public class Service extends android.app.Service implements Runnable, Valores{

private static final int MAX = 10;
protected int cont = 0;
private boolean ativo;
private double numero1;
private double numero2;
private double resultado;

private final IBinder conexao = new LocalBinder();

public class LocalBinder extends Binder{

    public Valores getValores(){
        return Service.this;
    }
}


public IBinder onBind(Intent intent){

    return conexao;
}



@Override
public void onCreate() {
    // TODO Auto-generated method stub
    Log.i("and","onCreate()");

    super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    // TODO Auto-generated method stub
    Log.i("and","onStartCommand()");
    //cont = 0;
    //ativo = true;
    //new Thread(this,"Exemplo Serviço: "+startId).start();

    String valor1 = intent.getStringExtra("numero1");
    String valor2 = intent.getStringExtra("numero2");
    Double numero1 = intent.getDoubleExtra("numero1", 0);
    resultado = Double.valueOf(valor1) + Double.valueOf(valor2);


    intent.putExtra("resultado", resultado);
    return super.onStartCommand(intent, flags, startId);
}


public void run(){
    while(ativo && cont < MAX){
        fazAlgo();
        Log.i("and", "Exemplo serviço executando: "+cont);
        cont++;
    }
}

public void fazAlgo(){
    try{
        Thread.sleep(1000);
    }catch(Exception e){
        e.printStackTrace();
    }
}

@Override
public void onDestroy() {
    // TODO Auto-generated method stub
    Log.i("and","onDestroy()");
    ativo = false;


    super.onDestroy();
}

@Override
public double numero1() {
    // TODO Auto-generated method stub
    return numero1;
}

@Override
public double numero2() {
    // TODO Auto-generated method stub
    return numero2;
}

@Override
public double resultado() {
    // TODO Auto-generated method stub
    return resultado;
}

}

    
asked by anonymous 21.02.2015 / 18:39

2 answers

1

Your activity needs to wait for the result to be ready, and the service needs to notify the activity when the calculation is complete. To do this, he needs to keep a reference of the activity, so he can know who he will warn. The way to do this is to register the activity as a service listener (default Observer ).

First, create the following interface:

public interface ReceptorDeResultado {
    public void receberResultado(double resultado);
}

Then, create an mListener attribute on the service that implements this interface (for information only, the letter m in the name is to differentiate from local variables. mListener is a member of the Service class - in this case, an instance variable):

private ReceptorDeResultado mListener = null;

Then modify the LocalBinder class to support registration and removal of the listener:

public class LocalBinder extends Binder{

    public Valores getService(){
        return Service.this;
    }

    public void registerListener(ReceptorDeResultado listener) {
        Service.this.mListener = listener;
    }

    public void unregisterListener() {
        Service.this.mListener = null;
    }
}

Now, make MainActivity implement the ResultReceiver interface:

public class MainActivity extends ActionBarActivity
        implements ServiceConnection, ReceptorDeResultado {

Because the MainActivity class now implements the ResultReceiver interface, it is required to have a getResult () method:

public void receberResultado() {
    // Neste método você faz uso do resultado, por exemplo exibindo-o
    // na tela. O código para fazer isso eu deixo por sua conta.
}

Now let's put into use the preparations we have made. But before that, in the MainActivity class, you need to make the binder variable an instance variable and not a local variable. So declare it along with the other instance variables:

private LocalBinder mBinder = null;

and correct its use in the onServiceConnected () method:

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    // TODO Auto-generated method stub
    mBinder = (LocalBinder) service;
}

Now let's go. Respecting the activity life cycle and avoiding memory leaks, we will register the activity as service listener and cancel this activity when the activity is destroyed. So in the same onServiceConnected () method we just moved:

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    LocalBinder binder = (LocalBinder) service;
    binder.registerListener(MainActivity.this);
}

Removing the listener in the onPause method (create this method in the MainActivity class):

@Override
public void onPause() {
    super.onPause();
    if (binder != null) {
        binder.unregisterListener();
    }
}

Registering the listener again in the onResume method, in case the activity has been destroyed and recreated for example in case of screen rotation (create the onResume () method in the MainActivity class):

@Override
public void onResume() {
    super.onResume();
    if (binder != null) {
        binder.registerListener(this);
    }
}

Ready, the service now has a listener (or observer) to tell you when the result is ready. Let's do this in the Service class:

resultado = Double.valueOf(valor1) + Double.valueOf(valor2);
if (mListener != null) {
    mListener.receberResultado(resultado);
}

Voilà! If everything went well (I did not test the code), the service will inform the calculated result to the activity (if it is watching the service at that moment).

Now a caveat for the future. For more time-consuming calculations, you will need your Service to perform this calculation on a separate thread (today it runs on the main thread and only allows fast calculations, which do not cause blocking of the main thread). The easiest way to allow this is to make your service extend the IntentService class and not android.app.Service. This is because IntentService executes commands on a separate thread by default. So, when you're implementing your most time-consuming calculations, study IntentService .

    
21.02.2015 / 20:52
1

The problem you are having is because you are recovering the values before the onStartCommand has ended, so the values were not calculated because the calculation is asynchronous.

My suggestion is to use a BroadcastReceiver local, without exposing private data outside of your application.

Remembering that you need to declare the dependency in the support library v4, if you can not, use BroadcastManager, but then it will be global.

My Service example that uses LocalBroadcastManager to notify the termination of an asynchronous job:

public class TheService extends Service {

    public static final String BROADCAST_FILTER = "the_service_broadcast";
    public static final String PARAMETER_EXTRA_KEY = "the_service_key";

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int result = super.onStartCommand(intent, flags, startId);

        new Thread() {
            @Override
            public void run() {
                try {
                    // Doing Heavy Work...
                    Thread.sleep(TimeUnit.SECONDS.toMillis(5));
                    broadcast(true);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    broadcast(false);
                }
            }
        }.start();

        return result;
    }

    void broadcast(boolean success) {
        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_FILTER).putExtra(PARAMETER_EXTRA_KEY, success));
    }
}

Example of Activity that registers a BroadcastReceiver for the broadcast of Service :

public class MainActivity extends Activity {

    // BroadcastReceiver responsavel por escutar o broadcast do Service
    BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "Broadcast from TheService(" + intent.getBooleanExtra(TheService.PARAMETER_EXTRA_KEY, false) + ")", Toast.LENGTH_LONG).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Todo seu codigo de inicializacao...
        startTheService();
    }

    @Override
    public void onResume() {
        super.onResume();

        // Registra o BroadcastReceiver para escutar por broadcast's do Service.
        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(TheService.BROADCAST_FILTER));
    }

    @Override
    public void onPause() {
        super.onPause();

        // Remove o Receiver quando a Activity for pausada.
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
    }

    private void startTheService() {
        // Inicia o servico
        startService(new Intent(this, TheService.class));
    }
}

I recommend using onPause to remove (avoid leaks from memory) and onResume to register. If your Activity is placed in background the BroadcastReceiver will be removed and if Activity returns to foreground BroadcastReceiver will be re-registered.

If you need the result even though Activity is not visible, then I recommend removing only the onStop or onDestroy . It would be valid to save the result, but it is not recommended to change the View's in these cases.

    
21.02.2015 / 20:23