Príklad AsyncTask pre Android

Čítal som o AsyncTask a vyskúšal som jednoduchý program uvedený nižšie. Ale zdá sa, že nefunguje. Ako to môžem spraviť, aby to fungovalo?

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

Snažím sa len zmeniť štítok po 5 sekundách v procese na pozadí.

Toto je môj súbor 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>
Riešenie

Dobre, snažíte sa získať prístup ku grafickému rozhraniu prostredníctvom iného vlákna. To v zásade nie je dobrý postup.

AsyncTask vykonáva všetko v doInBackground() vnútri iného vlákna, ktoré nemá prístup ku grafickému rozhraniu, kde sú vaše pohľady.

Funkcie preExecute() a postExecute() vám ponúkajú prístup ku grafickému používateľskému rozhraniu pred a po vykonaní ťažkej operácie v tomto novom vlákne, dokonca môžete výsledok dlhej operácie odovzdať funkcii postExecute(), aby sa potom zobrazili všetky výsledky spracovania.

Pozrite si tieto riadky, kde neskôr aktualizujete svoj TextView:

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

vložte ich do onPostExecute()

Potom uvidíte text vášho TextView aktualizovaný po dokončení doInBackground.

EDIT: Všimol som si, že váš poslucháč onClick nekontroluje, ktorý View bol vybraný. Zistil som, že najjednoduchší spôsob, ako to urobiť, je pomocou príkazov switch. Nižšie mám upravenú kompletnú triedu so všetkými návrhmi, aby som ušetril zmätok.

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
        }
    }
}
Komentáre (11)

Som si istý, že sa vykonáva správne, ale vy sa snažíte zmeniť prvky používateľského rozhrania vo vlákne na pozadí a to nejde.

Upravte svoje volanie a AsyncTask takto:

Trieda volania

Poznámka: Osobne odporúčam používať onPostExecute() všade tam, kde vykonávate vlákno AsyncTask, a nie v triede, ktorá rozširuje samotnú AsyncTask. Myslím, že to uľahčuje čítanie kódu, najmä ak potrebujete AsyncTask na viacerých miestach, ktoré spracúvajú výsledky mierne odlišne.

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

Trieda LongThread (rozširuje AsyncTask):

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

    return "Executed";
}      
Komentáre (2)

Presuňte tieto dva riadky:

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

z metódy doInBackground vašej AsyncTask'a vložte ich do metódy onPostExecute. Vaša AsyncTask by mala vyzerať takto:

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);
    }
}
Komentáre (2)