English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Handler
Ogni principiante dello sviluppo Android non può evitare di superare questo “ostacolo” chiamato Handler. Perché si dice che sia un ostacolo? Prima di tutto, è uno dei segreti dell'architettura Android, poi la maggior parte delle persone conoscono il fatto ma non la ragione. Dopo aver visto il metodo Handler.post, ho deciso di esplorare il codice sorgente per comprendere meglio il meccanismo di implementazione di Handler.
Aggiornamento UI asincrono
Iniziamo con un mantra obbligatorio: “La thread principale non deve eseguire operazioni lunghe, la thread secondaria non deve aggiornare l'UI”. Questa regola dovrebbe essere conosciuta da chiunque inizi a studiare, ma come risolvere il problema del mantra? Ecco quando Handler appare davanti a noi (AsyncTask funziona anche, ma in realtà è un encapsulamento di Handler), ecco un codice classico e comune (ignoriamo il problema di memoria in questo momento, ne parleremo più tardi):
Prima di tutto, crea un nuovo handler nell'Activity:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: mTestTV.setText("This is handleMessage");//更新UI break; } } };
然后在子线程里发送消息:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络 mHandler.sendEmptyMessage(0); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
至此完成了在子线程的耗时操作完成后在主线程异步更新UI,可是并没有用上标题的post,我们再来看post的版本:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络 Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { mTestTV.setText("This is post");//更新UI } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
从表面上来看,给post方法传了个Runnable,像是开了个子线程,可是在子线程里并不能更新UI啊,那么问题来了,这是怎么个情况呢?带着这个疑惑,来翻翻Handler的源码:
先来看看普通的sendEmptyMessage是什么样子:
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
然后将我们传入的参数封装成了一个消息,然后调用sendMessageDelayed:
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
Then call sendMessageAtTime again:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
Well, let's take a look at post():
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);//The difference between getPostMessage(r) and sendMessage is that }
The method only has one line, the internal implementation is the same as the ordinary sendMessage, but there is only one difference, that is, the getPostMessage(r) method:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
This method also found that the parameters we pass in are encapsulated into a message, but this time it is m.callback = r, just before msg.what = what, and we don't look at these properties of Message
Android message mechanism
Here, we just know that the principles of post and sendMessage are both encapsulated into Message, but we still don't know what the whole mechanism of Handler is like, let's continue to explore.
Just seen those two methods finally call sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
Questo metodo chiama di nuovo enqueueMessage, dal nome sembra che sia per aggiungere messaggi alla coda, guardiamo dentro:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
Non preoccupiamoci di mAsynchronous, che riguarda l'asincronia, continuiamo a passare i parametri al metodo enqueueMessage di queue, riguardo all'assegnazione di msg.target vedremo più tardi, entriamo nel metodo enqueueMessage della classe MessageQueue, il metodo è lungo, guardiamo le righe chiave:
Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg;
Proprio come suggerisce il nome del metodo, un ciclo infinito aggiunge messaggi alla coda di messaggi (in forma di elenco), ma se si mette c'è anche da prendere, come si può prendere questo messaggio?
Guardando il metodo MessageQueue, abbiamo trovato next(), il codice è troppo lungo per spiegare, ma sappiamo che serve per estrarre i messaggi. Ma in che parte viene chiamato questo metodo? Non nel Handler, abbiamo trovato il personaggio chiave Looper, lo chiamo 'messaggero circolare', specializzato nel prendere messaggi dalla coda di messaggi, il codice chiave è il seguente:
for (;;) { Message msg = queue.next(); // potrebbe bloccarsi ... msg.target.dispatchMessage(msg); ... msg.recycleUnchecked(); }
Semplicemente chiaro, abbiamo visto il msg.target di cui abbiamo parlato prima, prima nel Handler abbiamo assegnato msg.target = this, quindi guardiamo il dispatchMessage nel Handler:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
1. Se msg.callback non è vuoto, chiama il metodo handleCallback (message.callback.run())
2. Se mCallback non è vuoto, chiama mCallback.handleMessage(msg)
3. Infine, se tutto è vuoto, esegui il metodo handleMessage(msg) dell'Handler stesso
Il callback di msg dovrebbe essere abbastanza intuitivo, è il metodo run del Runnable che passiamo tramite Handler.post(Runnable r), qui dobbiamo parlare delle basi di Java, chiamare direttamente il metodo run della thread è come chiamare un metodo in una classe comune, viene eseguito nella thread corrente e non avvia una nuova thread.
Quindi arrivo qui, abbiamo risolto il dubbio iniziale, perché in post abbiamo passato un Runnable ma possiamo ancora aggiornare l'UI sulla thread principale.
Continua a leggere se msg.callback è vuoto, qual è il mCallback, è necessario guardare il costruttore:
1. public Handler() { this(null, false); } 2. public Handler(Callback callback) { this(callback, false); } 3. public Handler(Looper looper) { this(looper, null, false); } 4. public Handler(Looper looper, Callback callback) { this(looper, callback, false); } 5. public Handler(boolean async) { this(null, async); } 6. public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) {}} final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "La seguente classe Handler dovrebbe essere statica o potrebbero verificarsi perdite: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Impossibile creare un handler all'interno di un thread che non ha chiamato Looper.prepare()" } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 7. public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
La realizzazione dettagliata è solo per gli ultimi due, già sappiamo come è venuto fuori mCallback, possiamo passarlo nel costruttore.
Infine, se entrambi i callback sono vuoti, eseguiamo il metodo handleMessage(msg) del Handler stesso, ovvero il metodo handleMessage che abbiamo sovrascritto quando abbiamo creato il nuovo Handler.
Looper
Vediamo qui una domanda, ovvero non abbiamo passato alcun parametro quando abbiamo creato il Handler, né c'è da sapere dove è stato chiamato un metodo di Looper, dove si trova la creazione di Looper e le chiamate ai metodi? In realtà, tutto questo è già stato fatto da Android per noi, possiamo trovarlo nel metodo main dell'ActivityThread, il punto di ingresso del programma:
public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); ...
Riassunto
Abbiamo fatto una panoramica generale del meccanismo di messaggi di Handler, nonché delle differenze tra i metodi post e sendMessage che utilizziamo di solito. Ecco un riassunto, che coinvolge quattro classi: Handler, Message, MessageQueue, Looper:
Creare un nuovo Handler, inviare messaggi tramite sendMessage o post, Handler chiama sendMessageAtTime per passare i Message a MessageQueue
Il metodo enqueueMessage di MessageQueue inserisce i Message in coda come una lista
Il metodo loop di Looper chiama ricorsivamente MessageQueue.next() per estrarre i messaggi e chiama dispatchMessage di Handler per elaborare i messaggi
In dispatchMessage, si verifica se msg.callback o mCallback, ovvero il metodo post o il costruttore passato non sono vuoti e si eseguono i loro callback. Se sono tutti vuoti, si esegue il più comune handleMessage che abbiamo sovrascritto.
Parliamo infine del problema di perdita di memoria del handler
Ecco il codice che abbiamo utilizzato per creare il nostro nuovo Handler:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { ... } };
Quando si utilizza una classe interna (inclusi i classi anonime) per creare un Handler, l'oggetto Handler mantiene implicitamente un riferimento all'Activity.
E 'Handler' di solito si accompagna a un thread in background che richiede molto tempo, questo thread invia messaggi per aggiornare l'UI una volta completato il compito. Tuttavia, se l'utente chiude l'Activity durante la richiesta di rete, normalmente, l'Activity non viene utilizzata più e potrebbe essere raccolta durante il controllo GC, ma poiché a quel punto il thread non è ancora stato eseguito, e questo thread tiene una riferimento al Handler (altrimenti come potrebbe inviare messaggi al Handler?), questo Handler tiene una riferimento all'Activity, il che porta all'impossibilità di raccogliere l'Activity (cioè una perdita di memoria), fino alla fine della richiesta di rete.
Inoltre, se è stato eseguito il metodo postDelayed() di Handler, prima che il delay impostato arrivi, ci sarà una catena MessageQueue -> Message -> Handler -> Activity, che farà sì che il tuo Activity venga mantenuto in memoria e non possa essere riciclato.
Una delle soluzioni è l'uso di riferimenti deboli:
static class MyHandler extends Handler { WeakReference<Activity > mActivityReference; MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } }
Ecco la raccolta di informazioni sulla meccanica di messaggi handler di Android, ulteriori informazioni saranno aggiunte in seguito, grazie per il supporto del nostro sito!
Dichiarazione: il contenuto di questo articolo è stato prelevato da Internet, è di proprietà dei rispettivi detentori del diritto d'autore, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, il sito web non detiene il diritto di proprietà, non è stato sottoposto a elaborazione editoriale umana e non assume responsabilità per le relative responsabilità legali. Se trovi contenuti sospetti di violazione del diritto d'autore, è possibile inviare una e-mail a notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata, il sito web eliminerà immediatamente il contenuto sospetto di violazione del diritto d'autore.