W tym samouczku wyjaśnimy jak Android działa podczas uruchamiania usługi, opiszemy, z czego składają się wątki wykonania i o co chodzi w procesach. Pozwoli nam to zrozumieć sposób działania naszych aplikacji, dając nam większą kontrolę i stabilność na urządzeniach mobilnych, na których zostaną zainstalowane.
Wątek
Gdy użytkownik uruchamia aplikację, Android tworzy wątek o nazwie main (main). Ten wątek jest bardzo ważny, ponieważ odpowiada za zarządzanie zdarzeniami, które użytkownik wyzwala w odpowiednich komponentach, a także obejmuje zdarzenia, które rysują ekran. Wątek wykonania, najmniejsza część, która może zostać przetworzona przez program planujący w systemie operacyjnym, w tym przypadku Android (z jądrem Linux).
ten implementacja wielu wątków które są przetwarzane w tym samym czasie w tej samej aplikacji (nazwijmy to współbieżnością, co odnosi się do jednoczesności wykonania), jest to znane jako wielowątkowość. Stosowana jest wielowątkowość, aby te wątki współdzieliły zasoby I na tym polega proces, pamiętaj, że można to zastosować programowo w kodzie tej samej aplikacji, implementacja wielowątkowości na poziomie systemu operacyjnego nie zależy od nas.
Początek cyklu życia aplikacji obejmuje wygenerowanie nowego procesu linuksowego, do którego przypisany jest wątek główny lub wątek interfejsu użytkownika (wątek odpowiedzialny za przetwarzanie graficzne aplikacji, wątek interfejsu użytkownika w języku angielskim).
NotatkaCykl życia obejmuje wykonanie metod: onCreate(), onStart() i onResume (); na początku i na końcu: onPause(), onStop() i onDestroy().
Proces może zostać wymuszony przez Androida z powodu braku pamięci, tego typu przypadek jest rzadki ze względu na zaawansowanie technologiczne, ale nadal się zdarza.
Pytanie brzmi: Które procesy Android postanawia zamknąć?
Są one zamykane przez porównanie ich poziomu ważności, podsumowane w następujący sposób:
Najważniejsze: procesy pierwszoplanoweUżytkownik wchodzi w interakcję ze wspomnianym procesem (jest aktualnie uruchomiona metoda onResume() wspomnianego procesu). Istnieje usługa, która uruchamia swoje metody cyklu życia. Czy jest tam? Odbiornik transmisji biegnie jego Metoda onReceive().
Drugie najważniejsze: Widoczne procesyAktywność z wezwaniem do Metoda onPause(). Usługa połączona z widoczną aktywnością (usługa związana).
Trzeci najważniejszy: proces z usługąUżytkownik nie wchodzi w bezpośrednią interakcję z procesem. Proces ma uruchomioną usługę w tle.
Drugie najmniej ważne: proces w tleNie ma rodzaju interakcji z użytkownikiem. Proces ostatnio oglądany przez użytkownika będzie ostatnim, który zostanie zniszczony.
Najmniej ważne: Pusty procesNie zawiera aktywnych składników. Proces jest nadal aktywny w celu buforowania, uniemożliwiając użytkownikowi powrót do korzystania z tego procesu.
Ten ostatni, pusty proces, jako pierwszy kończy się w przypadku braku pamięci. W związku z tym aplikacja, która implementuje usługę, w której tworzony jest wątek w celu pobierania treści z Internetu, będzie ważniejsza niż aplikacja, która tworzy wątek bez implementacji usługi, dzięki czemu istnieje większe prawdopodobieństwo, że zostanie ona zakończona przed zakończeniem pobierania. , ponieważ są to procesy długotrwałe.
Aby zrozumieć wielowątkowość zobaczmy, jak Android obsługuje swój główny wątek.
PROCES A ma interfejs użytkownika lub GŁÓWNY wątek, ten wątek obsługuje a kolejka wiadomości lub kolejka wiadomości, która działa, gdy wątek staje się bezczynny, kto się tym zajmuje? ten Looper.
Looper to klasa interfejsu użytkownika Java na Androida że wraz z Klasa obsługi, przetwarza zdarzenia interfejsu użytkownika, takie jak naciśnięcia przycisków, przerysowane ekrany i przełączniki orientacji. Zdarzenia mogą być również używane do ładowania zawartości do usługi HTTP, zmiany rozmiaru obrazów i wykonywania zdalnych żądań. Kluczową cechą tych klas jest to, że są w stanie zaimplementować wzorzec współbieżności.
ten Klasa Android Looper zawiera Kolejka wiadomości (kolejka wiadomości) i jest skojarzona tylko z tematem, z którego została utworzona. Należy pamiętać, że tego połączenia nie można zerwać i że lLooper nie można go dołączyć do żadnego innego wątku. Ponadto Looper znajduje się w pamięci lokalnej i można go wywołać tylko z metody statycznej. Metoda przemieszczania sprawdza, czy Looper jest już skojarzony z wątkiem, a następnie metoda statyczna tworzy Looper. Następnie można użyć pętli do sprawdzenia wiadomości w kolejce.
Jak dotąd rozumiemy kilka pojęć: proces, wątek, wątek interfejsu użytkownika, looper, ale nadal nie wiemy, dlaczego wielowątkowość.
Operacje długoterminowe
Za długi czas uważa się każdą metodę, której wykonanie przekracza 5 sekund, co powoduje wyświetlenie typowego komunikatu „aplikacja nie odpowiada. Czy chcesz to zamknąć?
Czym mogą być te operacje?: Dostęp do Internetu, Zapytania SQL, parsowanie XML / HTML / JSON, złożone przetwarzanie grafiki. Każda z tych operacji, które są uruchamiane w głównym wątku, zablokuje go, a ponieważ to on obsługuje graficzny interfejs użytkownika, jest interpretowany jako zamrożenie, które Android postanawia zamknąć.
Wyobraźmy sobie, że każda z tych operacji trwa 7 sekund, a użytkownik postanawia coś napisać w jakimś tekście, więc chociaż te 7 sekund nie upłynęło, wątek interfejsu użytkownika nie może zaktualizować widoku, aby użytkownik docenił, że pisze, i więc generuje zamrożenie, wyzwalany jest komunikat "brak odpowiedzi", z którym masz dwie opcje, czekać lub zniszczyć, chociaż nigdy nie wiesz, jak długo czekać, może to być kilka sekund lub nawet minut w zależności od kolejki wiadomości które mają główny wątek.
Jak uniknąć zamarzania?
Korzystając z wątków lub usług, w zależności od tego, czy zadanie wymaga modyfikacji widoku, w tym przypadku zaimplementowana jest usługa, ponieważ widok aplikacji nie może być modyfikowany poza wątkiem interfejsu użytkownika. Najlepszym sposobem uniknięcia zamrożenia jest użycie zadań asynchronicznych z klasą AsyncTask. W tym samouczku zaimplementujemy wiele wątków, aby zrozumieć zachowanie architektury systemu Android.
Kod i rozwój
Projekt, który stworzymy dalej będzie się opierać na pobraniu obrazu za pomocą którego musimy stworzyć wątek, który pozwoli nam zarządzać dostępem i pobieraniem przez internet, ponieważ GŁÓWNY lub Wątek interfejsu użytkownika nie zezwala na to działanie.
Zaczniemy od stworzenia nowego projektu z pustą aktywnością, którą nazwaliśmy ten projekt „Przykład wielowątkowy”, za pomocą jednej prostej czynności stworzymy strukturę pliku XML które należy do tej działalności.
Mamy pole tekstowe, przycisk, układ liniowy odpowiadający nieokreślonemu paskowi ładowania, którego użyjemy później, oraz widok listy zawierający tablicę adresów URL obrazów hostowanych w Internecie. W pliku, który zawiera klasę Java dla naszej (unikalnej) działalności, jest ona zapisana następującym kodem:
pakiet com.omglabs.multithreaexample; importuj android.support.v7.app.AppCompatActivity; importuj android.os.Bundle; importuj android.view.View; importuj android.widget.AdapterView; importuj android.widget.EditText; importuj android.widget.LinearLayout; importuj android.widget.ListView; importuj android.widget.ProgressBar; klasa publiczna MainActivity extends AppCompatActivity implementuje AdapterView.OnItemClickListener {private EditText editText; prywatny ListView listView; prywatne String [] adresy URL; prywatny ProgressBar progressBar; prywatny postęp LinearLayoutLayout; @Zastąp chronione void onCreate (Pakiet protectedInstanceState) {super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); editText = (EditText) findViewById (R.id.downloadURL); listView = (ListView) findViewById (R.id.listurls); listView.setOnItemClickListener (to); adresy URL = getResources ().getStringArray (R.array.URLs); progressBar = (ProgressBar) findViewById (R.id.progressbar); progressLayout = (LinearLayout) findViewById (R.id.progresslayout); } public void download (widok widoku) {} @Override public void onItemClick (AdapterView adapterView, widok widoku, int i, long l) {editText.setText (urls [i]); }}Do tej pory aplikację można skompilować bez problemu, w tej klasie deklarujemy zmienne:
- edytować tekst
- listaWidok
- adresy URL
- pasek postępu
- postępUkład
Pole tekstowe, lista, układ ciągów, pasek postępu i układ liniowy.
w Metoda onCreate Przypisujemy im odpowiedni widok, który do nich należy i który został utworzony w pliku XML aktywności, z wyjątkiem adresów url, które przypisują jej wartości z folderu wartości w pliku string i których rozmieszczenie jest zadeklarowane następująco:
http://www.fmdos.cl/wp-content/uploads/2016/03/1.jpg.webp http://vignette3.wikia.nocookie.net/teenwolf/images/9/90/Crystal_Reed_003.jpeg.webp https: // pbs.twimg.com/profile_images/699667844129107968/EvhTFBHN.jpg.webp http://vignette1.wikia.nocookie.net/teen-wolf-pack/images/0/0b/Holland-holland-roden-31699868-500-600.png.webpPusta metoda pobierania (widok widoku) zostanie wypełniona kodem, który wykona pobieranie i który jest połączony z Przycisk pobierania Bot poprzez atrybut onclick. Wreszcie metoda onetemclick który należy do widok listy, wypełnia pole tekstowe po kliknięciu dowolnego z adresów URL zawartych na liście. Po skompilowaniu ten kod będzie wyglądał tak:
W następnym kroku stworzymy metody, które przejdą do pobrania, wykonując następujące kroki:
- Utwórz obiekt klasy URL (java.net), który będzie reprezentował adres URL do pobrania.
- Otwórz połączenie za pomocą tego obiektu.
- Odczytaj dane (przez sieć) za pomocą klasy strumienia wejściowego w tablicy bajtów.
- Otwórz / utwórz plik strumienia wyjściowego, w którym dane URL zostaną zapisane na karcie SD.
- Zapisz dane do tego pliku.
- I wreszcie zamknij połączenie.
Na razie będzie to wyglądać tak:
public boolean download usingThreads (String link) {potwierdzenie logiczne = false; Link do pobrania adresu URL = null; Połączenie HttpURLConnection = null; InputStream inputStream = null; spróbuj {downloadLink = nowy adres URL (link); połączenie = (HttpURLConnection) downloadLink.openConnection (); inputStream = conne.getInputStream (); } catch (MalformedURLException e) {e.printStackTrace (); } catch (IOException e) {e.printStackTrace (); } w końcu {if (connex! = null) {connex.disconnect (); } if (inputStream! = null) {spróbuj {inputStream.close (); } catch (IOException e) {e.printStackTrace (); }}} potwierdzenie zwrotu; }Ta metoda, którą zbudowaliśmy, będzie potrzebować tylko Strunowy który będzie adresem URL do pobrania, to logiczne Aby potwierdzić pobieranie, downloadLink to obiekt URL, connection to połączenie, które zostanie nawiązane w celu uzyskania dostępu do obiektu, a inputStream to ten, który przejdzie do odczytu danych, jeśli spróbujemy użyć tej metody na przycisku pobierzBot aplikacja zatrzymałaby się z powodu braku możliwości uruchomienia na główny wątek.
Tutaj idziemy z użyciem wątków, są dwa sposoby na zrobienie tego z klasą i jest to poprzez rozszerzenie tej klasy do Thread lub zaimplementowanie klasy Runnable, ta klasa nie jest wątkiem, po prostu pozwala stworzyć metodę, którą można uruchomić w konkretnym momencie i jeśli utworzysz osobny wątek uruchom go w nim.
Wewnątrz przycisku pobierania napiszemy ten kod, który będzie wyglądał tak:
public void download (Widok widoku) {Wątek mThread = nowy wątek (nowy mRunn ()); mWątek.start (); }Tutaj tworzymy nowy wątek, który potrzebuje obiektu Runnable, który tworzymy w prywatnej klasie, tak jak to:
klasa prywatna mRunn implementuje Runnable {@Override public void run () {pobierz usingThreads (urls [0]); }}Utwórz zajęcia prywatne
NotatkaPamiętaj, że to wszystko jest w klasie Java naszej jedynej działalności.
Z linią:
pobierz za pomocą wątków (adresy URL [0]);Wywołujemy funkcję, którą utworzyliśmy w miejscu, w którym otworzyliśmy połączenie, przekazuje się do niej element tablicy URL, aby mógł odczytać dane z tego adresu. Później zostanie zmodyfikowany.
Gdybyśmy próbowali uruchomić tę aplikację przez naciśnięcie przycisku, aplikacja zatrzymałaby się, ponieważ potrzebujemy specjalnego zezwolenia na dostęp do Internetu, o które prosimy poprzez manifest naszej aplikacji. Dodanie linii przed etykietą:
Teraz, aby sprawdzić, czy aplikacja faktycznie wykonuje pobieranie, dodamy kilka linijek kodu do metoda pobierania za pomocą wątków, będzie to wyglądać tak:
public boolean download usingThreads (String link) {potwierdzenie logiczne = false; Link do pobrania adresu URL = null; Połączenie HttpURLConnection = null; InputStream inputStream = null; FileOutputStream archOutputStream = null; plik plik = null; spróbuj {downloadLink = nowy adres URL (link); połączenie = (HttpURLConnection) downloadLink.openConnection (); inputStream = conne.getInputStream (); file = nowy plik (Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS) + "/" + Uri.parse (link) .getLastPathSegment ()); archOutputStream = nowy FileOutputStream (plik); int Odczyt = -1; bajt [] bufor = nowy bajt [1024]; while ((Odczyt = inputStream.read (bufor))! = -1) {archOutputStream.write (bufor, 0, Odczyt); } potwierdzenie = prawda; } catch (MalformedURLException e) {e.printStackTrace (); } catch (IOException e) {e.printStackTrace (); } w końcu {if (connex! = null) {connex.disconnect (); } if (inputStream! = null) {spróbuj {inputStream.close (); } catch (IOException e) {e.printStackTrace (); }} if (archOutputStream! = null) {spróbuj {archOutputStream.close (); } catch (IOException e) {e.printStackTrace (); }}} potwierdzenie zwrotu; } FileOutputStream archOutputStream = null; plik plik = null;Deklaracje tych obiektów reprezentują zapis pliku, który jest odczytywany, oraz pusty plik, w którym zostanie zapisany odczyt.
file = nowy plik (Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS) + "/" + Uri.parse (adresy URL [0]).getLastPathSegment ()); archOutputStream = nowy FileOutputStream (plik); int Odczyt = -1; bajt [] bufor = nowy bajt [1024]; while ((Odczyt = inputStream.read (bufor))! = -1) {archOutputStream.write (bufor, 0, Odczyt); } potwierdzenie = prawda;„File” to pusty obiekt File, którego adres jest tworzony przez uzyskanie dostępu do karty SD „Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS)” i dodanie ukośnika „/” i ostatniego segmentu adresu URL, który ogólnie reprezentuje nazwę pliku do download , osiągamy to za pomocą metody getLastPathSegment().
Przed testowaniem aplikacji dodamy ostatnie uprawnienie w manifeście:
Po uruchomieniu aplikacji na emulatorze lub urządzeniu z systemem Android, po naciśnięciu przycisku zobaczymy, że pozornie nic się nie dzieje, ale jeśli sprawdzimy folder Download eksploratorem plików, zorientujemy się, że pierwszy element na liście został pobrany; zdjęcie o nazwie 1.jpg.webp.
Aby to zrobić dynamiczną aplikację i zaimplementuj adresy URL widoku listy, zaktualizujemy metoda pobierania (widok widoku) i dodamy to, jako pierwszy wiersz:
Link ciągu = editText.getText ().ToString ();A w Klasa mRunn dodamy to przed metodą run():
klasa prywatna mRunn implementuje Runnable {private String link; publiczny mRunn (link ciągu) {ten.link = link; } @Override public void run () {pobierz usingThreads (link); }}A w Klasa mRunn dodamy to przed metodą run():
Możemy więc przekazać zmienną link z pola tekstowego do metody, która wykonuje pobieranie. Aplikacja w tym momencie jest w pełni funkcjonalna, chociaż brakuje jej trochę przyjazności dla użytkownika, więc postaramy się to naprawić, korzystając z paska postępu, który zadeklarowaliśmy na początku.
W klasie mRunn w metodzie run() zawrzemy:
MainActivity.this.runOnUiThread (nowy Runnable() {@Override public void run() {progressLayout.setVisibility (View.VISIBLE);}});Przed wezwaniem do downloadusandoThreads. Spowoduje to, że pasek ładowania pojawi się, gdy naciśniemy przycisk, w ostatniej klauzuli metoda pobierania za pomocą wątków.
Dodamy:
this.runOnUiThread (nowy Runnable() {@Override public void run() {progressLayout.setVisibility (View.GONE);}});Więc po zakończeniu pobierania pasek ponownie znika. Nastąpi to niezależnie od tego, czy pobieranie się powiedzie.
I to już wszystko, jedno krótka implementacja wielu wątkówJest to trochę żmudne i przynosi pewne komplikacje w przypadku bardziej złożonych aplikacji.Najskuteczniejszym sposobem wykonania tego zadania, w naszym przypadku pobrania niektórych obrazów, jest użycie Zadania asynchroniczne.