Android - najlepszy i bezpieczny sposób na zatrzymanie wątku


79

Chcę wiedzieć, jaki jest najlepszy sposób na zatrzymanie wątku w systemie Android. Wiem, że mogę AsyncTaskzamiast tego użyć i że jest cancel()metoda. Muszę użyć Threads w mojej sytuacji. Oto jak używam Thread:

Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //doing some work
        }
    };
new Thread(runnable).start();

Więc czy ktoś ma pomysł, który jest najlepszy sposób na zatrzymanie wątku?

Odpowiedzi:


90

Powinieneś sprawić, by twój wątek obsługiwał przerwania. Zasadniczo możesz wywołać, yourThread.interrupt()aby zatrzymać wątek, aw metodzie run () będziesz musiał okresowo sprawdzać stanThread.interrupted()

Jest dobry tutorial tutaj .


Dzięki, cały czas szukałem anulowania () lub zatrzymania ().
Miloš Černilovský

stop()Metoda @ MilošČernilovský jest przestarzała i nie należy jej już używać.
Alston

29

Ta sytuacja nie różni się w żaden sposób od standardowej Java. Możesz użyć standardowego sposobu zatrzymania wątku:

class WorkerThread extends Thread {
    volatile boolean running = true;

    public void run() {
        // Do work...
        if (!running) return;
        //Continue doing the work
    }
}

Głównym pomysłem jest od czasu do czasu sprawdzanie wartości pola. Kiedy musisz zatrzymać wątek, ustawiasz runningna fałsz. Ponadto, jak zauważył Chris, możesz skorzystać z mechanizmu przerywania.

Nawiasem mówiąc, gdy używasz AsyncTask, twoje apporach nie będą się zbytnio różnić. Jedyną różnicą jest to, że będziesz musiał wywołać isCancel()metodę ze swojego zadania, zamiast mieć specjalne pole. Jeśli wywołasz cancel(true), ale nie zaimplementujesz tego mechanizmu, wątek nadal nie zatrzyma się samoczynnie, będzie działał do końca.


23

W systemie Android obowiązują te same zasady, co w normalnym środowisku Java. W Javie wątki nie są zabijane, ale zatrzymywanie wątku odbywa się w sposób kooperacyjny . Wątek jest proszony o zakończenie, a następnie można go bezpiecznie zamknąć.

Często volatile booleanużywane jest pole, które wątek okresowo sprawdza i kończy się, gdy zostanie ustawiona na odpowiednią wartość.

Nie użyłbym a booleando sprawdzenia, czy wątek powinien się zakończyć . Jeśli użyjesz volatilejako modyfikatora pola, będzie to działać niezawodnie, ale jeśli twój kod stanie się bardziej złożony, ponieważ zamiast tego użyjesz innych metod blokujących wewnątrz whilepętli, może się zdarzyć, że twój kod w ogóle się nie zakończy lub przynajmniej zajmie więcej czasu . może chcieć.

Niektóre metody bibliotek blokujących obsługują przerwanie.

Każdy wątek ma już stan przerwania flagi boolowskiej i powinieneś to wykorzystać. Można to zaimplementować w następujący sposób:

public void run() {

   try {
      while(!Thread.currentThread().isInterrupted()) {
         // ...
      }
   } catch (InterruptedException consumed)
      /* Allow thread to exit */
   }

}

public void cancel() { interrupt(); }

Kod źródłowy pobrany z Java Concurrency in Practice . Ponieważ cancel()metoda jest publiczna, możesz pozwolić innemu wątkowi wywołać tę metodę, jak chcesz.

Istnieje również słabo nazwana metoda statyczna, interruptedktóra czyści stan przerwania bieżącego wątku.


2
W Android Studio jest napisane, że wyjątek „java.lang.InterruptedException” nigdy nie jest zgłaszany w odpowiednim bloku try?
CraPo

1
@CraPo Dodaj to przed pętlą while:if (Thread.interrupted()) { throw new InterruptedException();}
Kosso

16

Thread.stop()Metody, które mogą być stosowane do zatrzymania nić została zastąpiona; aby uzyskać więcej informacji, zobacz; Dlaczego Thread.stop, Thread.suspend i Thread.resume są przestarzałe? .

Najlepszym rozwiązaniem jest posiadanie zmiennej, z którą sam wątek konsultuje się i dobrowolnie wychodzi, jeśli zmienna ma określoną wartość. Następnie manipulujesz zmienną w kodzie, gdy chcesz, aby wątek został zamknięty. Oczywiście możesz też AsyncTaskzamiast tego użyć pliku .


4
AsyncTask nie jest tak naprawdę alternatywą dla mechanizmu przerywania, jest raczej instrumentem do interakcji z interfejsem użytkownika. Jak wskazałem w mojej odpowiedzi, nadal będziesz musiał zaimplementować ten sam mechanizm w AsyncTask, co w każdym wspólnym wątku, jeśli chcesz mieć możliwość zatrzymania go podczas wykonywania.
Malcolm,

2

Obecnie niestety nie możemy nic zrobić, aby zatrzymać wątek ....

Dodając coś do odpowiedzi Matta, możemy zadzwonić, interrupt()ale to nie zatrzymuje wątku ... Po prostu mówi systemowi, aby zatrzymał wątek, gdy system chce zabić niektóre wątki. Resztę wykonuje system i możemy to sprawdzić dzwoniąc interrupted().

[ps: Jeśli naprawdę wybierasz się z interrupt()tobą, poproszę cię o wykonanie kilku eksperymentów z krótkim snem po telefonie interrupt()]


2

Spróbuj w ten sposób

Thread thread = new Thread() {
    @Override
    public void run() {
        Looper.prepare();   
        while(true){ 
            Log.d("Current Thread", "Running"); 
            try{
               Thread.sleep(1000);
            }catch(Exeption exception){ }
        }
    }
};

thread.start();
thread.interrupt();

1

Istnieją 2 preferowane sposoby zatrzymania wątku.

  1. Utwórz niestabilną zmienną boolowską i zmień jej wartość na false i sprawdź wewnątrz wątku.

    volatile isRunning = false;
    
    public void run() {
    if(!isRunning) {return;}       
    
    }
    
  2. Lub możesz użyć metody przerwania (), która może zostać odebrana wewnątrz wątku.

    SomeThread.interrupt();
    
    public void run() {
    if(Thread.currentThread.isInterrupted()) {return;}
     }
    


0

To zadziałało dla mnie w ten sposób. Wprowadź zmienną statyczną do głównego działania i regularnie sprawdzaj, jak to zrobiłem, poniżej.

public class MainActivity extends AppCompatActivity {


//This is the static variable introduced in main activity
public static boolean stopThread =false;



  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Thread thread = new Thread(new Thread1());
    thread.start();

    Button stp_thread= findViewById(R.id.button_stop);

    stp_thread.setOnClickListener(new Button.OnClickListener(){
           @Override
           public void onClick(View v){
              stopThread = true;
           }

     }

  }


  class Thread1 implements Runnable{

        public void run() {

        // YOU CAN DO IT ON BELOW WAY 
        while(!MainActivity.stopThread) {
             Do Something here
        }


        //OR YOU CAN CALL RETURN AFTER EVERY LINE LIKE BELOW

        process 1 goes here;

        //Below method also could be used
        if(stopThread==true){
            return ;
        }
        // use this after every line



        process 2 goes here;


        //Below method also could be used
        if(stopThread==true){
            return ;
        }
        // use this after every line


        process 3 goes here;


        //Below method also could be used
        if(stopThread==true){
            return ;
        }
        // use this after every line


        process 4 goes here;



       }
   }

}

0

Jeśli w Twoim projekcie istnieje klasa wątku z programem obsługi, to gdy zaczynasz od jednej z klas fragmentów, jeśli chcesz się tutaj zatrzymać, jest rozwiązanie, jak zatrzymać i uniknąć awarii aplikacji, gdy fragment zostanie usunięty ze stosu.

Ten kod jest w Kotlinie . Działa doskonale.

class NewsFragment : Fragment() {

    private var mGetRSSFeedsThread: GetRSSFeedsThread? = null

    private val mHandler = object : Handler() {

        override fun handleMessage(msg: Message?) {


            if (msg?.what == GetRSSFeedsThread.GETRSSFEEDSTHREAD_SUCCESS) {
                val updateXMLdata = msg.obj as String
                if (!updateXMLdata.isNullOrEmpty())
                    parseUpdatePager(CommonUtils.getJSONObjectFromXML(updateXMLdata).toString())

            } else if (msg?.what == GetRSSFeedsThread.GETRSSFEEDSTHREAD_SUCCESS) {
                BaseActivity.make_toast(activity, resources.getString(R.string.pleaseTryAgain))
            }

        }
    }
    private var rootview: View? = null;
    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        rootview = inflater?.inflate(R.layout.fragment_news, container, false);

        news_listView = rootview?.findViewById(R.id.news_listView)
        mGetRSSFeedsThread = GetRSSFeedsThread(this.activity, mHandler)


        if (CommonUtils.isInternetAvailable(activity)) {
            mGetRSSFeedsThread?.start()
        }


        return rootview
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true);

    }



    override fun onAttach(context: Context?) {
        super.onAttach(context)
        println("onAttach")
    }

    override fun onPause() {
        super.onPause()
        println("onPause fragment may return to active state again")

        Thread.interrupted()

    }

    override fun onStart() {
        super.onStart()
        println("onStart")

    }

    override fun onResume() {
        super.onResume()
        println("onResume fragment may return to active state again")

    }

    override fun onDetach() {
        super.onDetach()
        println("onDetach fragment never return to active state again")

    }

    override fun onDestroy() {
        super.onDestroy()

        println("onDestroy fragment never return to active state again")
       //check the state of the task
        if (mGetRSSFeedsThread != null && mGetRSSFeedsThread?.isAlive!!) {
            mGetRSSFeedsThread?.interrupt();
        } else {

        }

    }

    override fun onDestroyView() {
        super.onDestroyView()
        println("onDestroyView fragment may return to active state again")



    }

    override fun onStop() {
        super.onStop()
        println("onStop fragment may return to active state again")



    }
}

Powyższy kod zatrzymuje działający wątek po przełączeniu na inny fragment lub aktywność z bieżącego fragmentu. również odtwarza się po powrocie do bieżącego fragmentu


0

Chodzi o to, że musisz sprawdzić, czy wątek działa, czy nie !?

Pole:

private boolean runningThread = false;

W wątku:

new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep((long) Math.floor(speed));
                        if (!runningThread) {
                            return;
                        }
                        yourWork();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

Jeśli chcesz przerwać wątek, powinieneś zrobić poniższe pole

private boolean runningThread = false;

2
To nie zatrzyma Thread, a tylko yourWork();zostanie wywołane. Aby to przetestować, możesz dodać Logponiżejpublic void run() {
KRK

@KRK Wiem to! i opublikowałem tę odpowiedź, ponieważ jest bardzo przydatna, gdy chcesz coś zatrzymać i rozpocząć od nowa, nie robiąc nic w wątku.
Notatka Hadi

@HadiNote KRK jest poprawne. Podajesz w swojej odpowiedzi If you want to stop the thread you should make the below field: private boolean runningThread = false;. To nie jest poprawne.
pookie

0

Moje wymaganie było nieco inne niż pytanie, ale jest to również przydatny sposób na zatrzymanie wątku w celu wykonania jego zadań. Chciałem tylko zatrzymać wątek po wyjściu z ekranu i wznowić go podczas powrotu do ekranu.

Zgodnie z dokumentacją systemu Android byłby to proponowany zamiennik metody stop , która została wycofana z interfejsu API 15

Wiele zastosowań stop powinno zostać zastąpionych kodem, który po prostu modyfikuje jakąś zmienną, aby wskazać, że wątek docelowy powinien przestać działać. Wątek docelowy powinien regularnie sprawdzać tę zmienną i wracać ze swojej metody uruchamiania w uporządkowany sposób, jeśli zmienna wskazuje, że ma przestać działać.

Moja klasa Thread

   class ThreadClass implements Runnable {
            ...
                  @Override
                        public void run() {
                            while (count < name.length()) {

                                if (!exited) // checks boolean  
                                  {
                                   // perform your task
                                                     }
            ...

OnStop i OnResume wyglądałyby tak

  @Override
    protected void onStop() {
        super.onStop();
        exited = true;
    }

    @Override
    protected void onResume() {
        super.onResume();
        exited = false;
    }

0

Ponieważ wiemy, że Thread.stop () jest przestarzała w JAVA, pod maską Thread.stop wywołuje metodę przerwania () w wątku, aby go zatrzymać, Przerwanie ma być wyrzucane z metod, które utrzymują wątek w oczekiwaniu na inny wątek do powiadomienia po zakończeniu wykonywania. Przerwanie nie spowoduje nic w wątku, jeśli nie jest obsługiwane podczas wykonywania wątku, na przykład, if(Thread.interrupted())return; w sumie musimy w zasadzie zarządzać początkiem i zatrzymaniem wątku, tak jak wywołanie start()metody takiej jak Thread.start()zaczyna się while(true)wewnątrz run()metody wątku i sprawdza stan przerwania w każdej iteracji i wraca z wątku.

Należy pamiętać, że wątek nie zginie w następujących sytuacjach:

  1. Wątek nie powrócił jeszcze z metody run ().
  2. Dostępne są wszystkie obiekty należące do wątku. (To sugeruje zerowanie / pozbycie się odniesień dla GC, aby wykonać resztę)

-2

Wewnątrz dowolnej klasy Activity tworzysz metodę, która przypisze NULL do instancji wątku, która może być używana jako alternatywa dla zdeprecycjonowanej metody stop () do zatrzymywania wykonywania wątku:

public class MyActivity extends Activity {

private Thread mThread;  

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);


        mThread =  new Thread(){
        @Override
        public void run(){
            // Perform thread commands...
    for (int i=0; i < 5000; i++)
    {
      // do something...
    }

    // Call the stopThread() method.
            stopThread(this);
          }
        };

    // Start the thread.
        mThread.start(); 
}

private synchronized void stopThread(Thread theThread)
{
    if (theThread != null)
    {
        theThread = null;
    }
}
}

U mnie to działa bez problemu.


11
Ustawienie Threadzmiennej na nullnie spowoduje zatrzymania wątku. Skierowanie argumentu do nullśrodka stopThreadto kompletna strata czasu.
Ted Hopp,

2
Jak to może zatrzymać wątek? Dlaczego są pozytywne głosy w tej sprawie?
mvd

1
To nie zatrzymuje wątku. Wątek nadal działa, a to przypisuje wartość null do wystąpienia wątku, uniemożliwiając sterowanie tym wątkiem, ponieważ utraciłeś instancję (która będzie działać do zakończenia procesu)
Teocci
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.