AsyncTask Android voorbeeld

Ik was aan het lezen over AsyncTask, en ik probeerde het simpele programma hieronder. Maar het lijkt niet te werken. Hoe kan ik het laten werken?

public class AsyncTaskActivity extends Activity {

    Button btn;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btn = (Button) findViewById(R.id.button1);
        btn.setOnClickListener((OnClickListener) this);
    }

    public void onClick(View view){
        new LongOperation().execute("");
    }

    private class LongOperation extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... params) {
            for(int i=0;i<5;i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            TextView txt = (TextView) findViewById(R.id.output);
            txt.setText("Executed");
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
        }

        @Override
        protected void onPreExecute() {
        }

        @Override
        protected void onProgressUpdate(Void... values) {
        }
    }
}

Ik probeer alleen het label na 5 seconden in het achtergrondproces te veranderen.

Dit is mijn main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:orientation="vertical" >
    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:indeterminate="false"
        android:max="10"
        android:padding="10dip">
    </ProgressBar>
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Progress" >
    </Button>
    <TextView android:id="@+id/output"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Replace"/>
</LinearLayout>
Oplossing

Ok, je probeert toegang te krijgen tot de GUI via een andere thread. Dit, in het algemeen, is geen goede praktijk.

De AsyncTask voert alles uit in doInBackground() in een andere thread, die geen toegang heeft tot de GUI waar je views zijn.

preExecute() en postExecute() bieden je toegang tot de GUI voor en na het zware werk in deze nieuwe thread, je kunt zelfs het resultaat van de lange operatie doorgeven aan postExecute() om vervolgens de resultaten van de verwerking te tonen.

Zie deze regels waar je later je TextView aan het updaten bent:

TextView txt = findViewById(R.id.output);
txt.setText("Executed");

zet ze in onPostExecute()

Je zult dan zien dat je TextView tekst is bijgewerkt nadat de doInBackground is voltooid.

EDIT: Het viel me op dat je onClick listener niet controleert welke View geselecteerd is. Ik vind de makkelijkste manier om dit te doen is via switch statements. Ik heb hieronder een complete class bewerkt met alle suggesties om verwarring te voorkomen.

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings.System;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.view.View.OnClickListener;

public class AsyncTaskActivity extends Activity implements OnClickListener {

    Button btn;
    AsyncTask<?, ?, ?> runningTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btn = findViewById(R.id.button1);
        // because we implement OnClickListener we only have to pass "this"
        // (much easier)
        btn.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        // detect the view that was "clicked"
        switch (view.getId()) {
        case R.id.button1:
            if (runningTask != null) runningTask.cancel(true);
            runningTask = new LongOperation();
            runningTask.execute();
            break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // cancel running task(s) to avoid memory leaks
        if (runningTask != null) runningTask.cancel(true);
    }

    private final class LongOperation extends AsyncTask {

        @Override
        protected String doInBackground(Void... params) {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // we were cancelled, stop sleeping!
                }
            }
            return "Executed";
        }

        @Override
        protected void onPostExecute(String result) {
            TextView txt = (TextView) findViewById(R.id.output);
            txt.setText("Executed"); // txt.setText(result);
            // might want to change "executed" for the returned string passed
            // into onPostExecute() but that is upto you
        }
    }
}
Commentaren (11)

Ik'weet zeker dat het goed wordt uitgevoerd, maar je'probeert de UI elementen in de achtergronddraad te veranderen en dat zal niet lukken.

Herzie je oproep en AsyncTask als volgt:

Aanroepende Klasse

Note: Ik stel persoonlijk voor om onPostExecute() te gebruiken waar je je AsyncTask thread ook uitvoert en niet in de class die AsyncTask zelf uitbreidt. Ik denk dat het de code makkelijker leesbaar maakt, vooral als je de AsyncTask op meerdere plaatsen nodig hebt en de resultaten iets anders afhandelt.

new LongThread() {
    @Override public void onPostExecute(String result) {
        TextView txt = (TextView) findViewById(R.id.output);
        txt.setText(result);
    }
}.execute("");

LongThread klasse (breidt AsyncTask uit):

@Override
protected String doInBackground(String... params) {
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    return "Executed";
}      
Commentaren (2)

Verplaats deze twee lijnen:

TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed");

uit je AsyncTask's doInBackground methode en zet ze in de onPostExecute methode. Je AsyncTask zou er ongeveer zo uit moeten zien:

private class LongOperation extends AsyncTask {

    @Override
    protected String doInBackground(String... params) {
        try {
            Thread.sleep(5000); // no need for a loop
        } catch (InterruptedException e) {
            Log.e("LongOperation", "Interrupted", e);
            return "Interrupted";
        }
        return "Executed";
    }      

    @Override
    protected void onPostExecute(String result) {               
        TextView txt = (TextView) findViewById(R.id.output);
        txt.setText(result);
    }
}
Commentaren (2)