Путь к изучению Android (13) Подробное объяснение Handler

1. Введение

Обработчик — это набор механизмов передачи сообщений Android, в основном используемый для межпотоковой связи.

Опишите это в самых простых терминах: Обработчик на самом деле является подпотоком, созданным основным потоком. Подпоток выполняется и генерирует сообщения. Looper получает сообщение и передает его обработчику. Обработчик получает сообщения в подпотоке. - нити одна за другой.

Binder/Socket используется для межпроцессного взаимодействия, а механизм сообщений Handler используется для межпотокового взаимодействия в одном и том же процессе.

Можно сказать, что везде, где есть асинхронный поток, взаимодействующий с основным потоком, должен быть и обработчик.

В сценариях многопоточных приложений информация об операции, которую необходимо обновить в рабочем потоке, передается в основной поток пользовательского интерфейса, чтобы рабочий поток мог обновить пользовательский интерфейс и, наконец, реализовать обработку асинхронных сообщений.

Основная цель использования механизма передачи сообщений Handler — обеспечить безопасность потоков при одновременном обновлении пользовательского интерфейса несколькими потоками.

2. Объяснение связанных понятий

Обработчик、Сообщение、Очередь сообщений、Петлер

  • Сообщение: представляет собой поведение или серию действий. Выполняемое. Каждое сообщение имеет четкий целевой обработчик, когда оно добавляется в очередь сообщений.
  • ThreadLocal: локальное хранилище потока (сокращенно TLS), каждый поток имеет свою собственную частную локальную область хранения, и разные потоки не могут получить доступ к области TLS друг друга. Функция ThreadLocal заключается в предоставлении локальных переменных TLS внутри потока. Эта переменная работает в течение жизненного цикла потока. Каждый поток имеет свое собственное значение (изоляция потока).
  • MessageQueue (реализован как на уровне C, так и на уровне Java): обеспечивает вставку и удаление в форме очереди. Его внутренняя структура хранит сообщения в виде двусвязного списка.
  • Looper (реализован как на уровне C, так и на уровне Java): Looper означает цикл. Он отвечает за циклическую выборку сообщений из очереди сообщений и последующую передачу сообщения обработчику для обработки. Handler: реальный процессор сообщения с возможностью получать сообщения, отправлять сообщения
    , обрабатывать сообщения, удалять сообщения и другие функции

Механизм обмена сообщениями Android:

  • Если взять в качестве примера метод sendMessage Handler, после отправки сообщения оно будет добавлено в очередь сообщений MessageQueue.
  • Looper отвечает за перемещение очереди сообщений и распределение сообщений в очереди соответствующему обработчику для обработки.
  • Сообщение обрабатывается в методе handleMessage обработчика, который завершает отправку и обработку сообщения.

Схема обработчика:

Модель механизма сообщений:

  • Сообщение: сообщение, которое необходимо передать, данные могут быть переданы;
  • MessageQueue: очередь сообщений, но ее внутренняя реализация не использует очередь. Фактически она поддерживает список сообщений через структуру данных односвязного списка, поскольку односвязные списки имеют преимущества при вставке и удалении. Основные функции — доставка сообщений в пул сообщений (MessageQueue.enqueueMessage) и удаление сообщений из пула сообщений (MessageQueue.next);
  • Handler: вспомогательный класс сообщения, его основная функция — отправка различных событий сообщений в пул сообщений (Handler.sendMessage) и обработка соответствующих событий сообщений (Handler.handleMessage);
  • Looper: непрерывно выполняется в цикле (Looper.loop), считывает сообщения из MessageQueue и распределяет сообщения целевому процессору в соответствии с механизмом распределения.

Архитектура механизма сообщений

  • После того, как дочерний поток завершит трудоемкую операцию, когда обработчик отправит сообщение, будет вызван MessageQueue.enqueueMessage для добавления сообщения в очередь сообщений.
  • При запуске цикла через Looper.loop сообщения будут непрерывно считываться из пула потоков, то есть вызывается MessageQueue.next.
  • Затем вызовите метод sendMessage целевого обработчика (то есть обработчика, отправившего сообщение), чтобы доставить сообщение, а затем вернитесь в поток, где находится обработчик. Целевой обработчик получает сообщение, вызывает метод handleMessage, получает сообщение и обрабатывает сообщение.

3. Базовое использование обработчика

3.1 Создать обработчик

Обработчик позволяет нам отправлять отложенные сообщения. Если пользователь закроет действие во время задержки, произойдет утечка действия.

Эта утечка связана с тем, что Message будет содержать обработчик, а из-за особенностей Java внутренний класс будет содержать внешний класс, так что Activity будет удерживаться обработчиком, что в конечном итоге приведет к утечке Activity.

Решение. Определите Handler как статический внутренний класс, сохраните внутреннюю слабую ссылку на Activity и своевременно удаляйте все сообщения.

public class HandlerActivity extends AppCompatActivity {

    private Button bt_handler_send;

    private static class MyHandler extends Handler {

        //弱引用持有HandlerActivity , GC 回收时会被回收掉
        private WeakReference<HandlerActivity> weakReference;

        public MyHandler(HandlerActivity activity) {
            this.weakReference = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = weakReference.get();
            super.handleMessage(msg);
            if (null != activity) {
                //执行业务逻辑
                Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.HandlerActivity);

        //创建 Handler
        final MyHandler handler = new MyHandler(this);

        bt_handler_send = findViewById(R.id.bt_handler_send);
        bt_handler_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //使用 handler 发送空消息
                        handler.sendEmptyMessage(0);

                    }
                }).start();
            }
        });
    }
    
    @Override
    protected void onDestroy() {
        //移除所有回调及消息
        myHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}

3.2 Получение сообщений

Вероятно, есть несколько способов получить сообщение:

Message message = myHandler.obtainMessage(); 		   //通过 Handler 实例获取
Message message1 = Message.obtain();   			      //通过 Message 获取
Message message2 = new Message();      				 //直接创建新的 Message 实例

Посмотрев исходный код, мы видим, что метод getMessage() класса Handler также вызывает метод get() объекта Message.

public final Message obtainMessage()
{
    return Message.obtain(this);
}

Просмотрев метод получения сообщения

public static Message obtain(Handler h) {
        //调用下面的方法获取 Message
        Message m = obtain();
        //将当前 Handler 指定给 message 的 target ,用来区分是哪个 Handler 的消息
        m.target = h;

        return m;
    }
    
//从消息池中拿取 Message,如果有则返回,否则创建新的 Message
public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

Чтобы сэкономить затраты, мы стараемся повторно использовать Message при его использовании и используем первые два метода для его создания.

3.3 Обработчик отправляет сообщение

Handler предоставляет нам ряд методов для отправки сообщений, таких как серия send() и серия post().Метод post должен передаваться в объект Runnalbe.Давайте посмотрим на исходный код метода post.

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

Но независимо от того, какой метод мы вызываем, в конечном итоге мы достигнем метода MessageQueue.enqueueMessage(Message,long).
В качестве примера возьмем метод sendEmptyMessage(int):

//Handler
sendEmptyMessage(int)
  -> sendEmptyMessageDelayed(int,int)
    -> sendMessageAtTime(Message,long)
      -> enqueueMessage(MessageQueue,Message,long)
  			-> queue.enqueueMessage(Message, long);

Отсюда можно найти очередь сообщений MessageQueue, которая отвечает за вход и выход сообщений в очередь.

4. Два экземпляра (основной поток – дочерний поток)

4.1 Подпоток к основному потоку

Сначала мы добавляем статический внутренний класс в MainActivity и переопределяем его метод handleMessage.

private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mTarget;

        public MyHandler(MainActivity activity) {
            mTarget = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            HandlerActivity activity = weakReference.get();
            super.handleMessage(msg);
            if (null != activity) {
                //执行业务逻辑
                if (msg.what == 0) {
                	Log.e("myhandler", "change textview");
                	MainActivity ma = mTarget.get();
	                ma.textView.setText("hahah");
            	}
                Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();
            }
        
        }
 }

Затем создайте частное свойство типа MyHandler:

private Handler handler1 = new MyHandler(this);

Наконец, создайте поток в обратном вызове onCreate для получения и отправки сообщений:

new Thread(new Runnable() {
            @Override
            public void run() {
                handler1.sendEmptyMessage(0);
            }
        }).start();

в заключение:

  • Объекты подкласса обработчика обычно создаются в основном потоке, чтобы к ним можно было получить доступ в обоих потоках. Мы создали подкласс класса Handler MyHandler и переопределили метод handlerMessage, который используется для получения и обработки отправленных сообщений. Затем мы создали дочерний поток, в котором использовали объект MyHandler для вызова метода sendEmptyMessage для отправки пустого сообщения. Затем мы можем получить эти данные в основном потоке.

4.2 От основного потока к дочернему потоку

Сначала создайте класс MyHandler.

private static class MyHandler extends Handler {

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0) {
                Log.e("child thread", "receive msg from main thread");
            }
        }
    }

Объявите частную переменную типа Handler и инициализируйте ее значением null по умолчанию.

private Handler handler1;

Создайте дочерний поток и укажите обработчику вновь созданный объект MyHandler.

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler1 = new MyHandler();
                Looper.loop();
                Log.e("child thread", "child thread end");
            }
        }).start();

Отправить сообщение из основного потока в дочерний поток

while (handler1 == null) {

        }

        handler1.sendEmptyMessage(0);
        handler1.getLooper().quitSafely();

5. Принцип механизма Handler

5.1 Обзор принципа обработчика

Обычные потоки не имеют циклистов. Если необходим объект циклителя, сначала необходимо вызвать метод Looper.prepare, а в потоке может быть только один циклизатор.

Как Handler осуществляет межпотоковую связь?

  • Android использует канальную связь в Linux
  • Что касается труб, то проще говоря, труба — это файл.
  • На обоих концах канала есть два открытых файловых дескриптора. Эти два открытых файловых дескриптора соответствуют одному и тому же файлу. Один из них используется для чтения, а другой — для записи.
  • Когда создается очередь сообщений
  • Вызовите функцию JNI, чтобы инициализировать объект NativeMessageQueue. NativeMessageQueue инициализирует объект Looper.
  • Функция Looper заключается в том, чтобы перевести основной поток приложения Android в состояние ожидания, когда в очереди сообщений уровня Java нет сообщения. Когда новое сообщение поступает в очередь сообщений уровня Java, оно пробуждает основной поток приложения Android. для обработки этих новостей

  • Обработчик отправляет сообщение в очередь MessageQueue через sendMessage()
  • Looper непрерывно извлекает сообщения, которые соответствуют условиям запуска, с помощью цикла() и передает сообщения целевому объекту для обработки.
  • После диспетчеризацииMessage() оно передается обратно в handleMessage() обработчика для соответствующей обработки.
  • При добавлении сообщения в MessageQueue запись символов в канал может разбудить поток цикла; если в MessageQueue нет сообщения и оно находится в состоянии Idle, будут выполнены методы в интерфейсе IdelHandler, что часто используется провести некоторую работу по уборке.

5.2 Обработчик и конвейерная связь

По сути каналы — это файлы, но они отличаются от обычных файлов: размер буфера канала обычно составляет 1 страницу, что составляет 4 КБ. Конвейер разделен на конец чтения и конец записи. Конец чтения отвечает за получение данных из канала и блокировку, когда данные пусты. Конец записи записывает данные в канал и блокирует, когда буфер канала заполнен.

  • В механизме Handler метод Looper.loop будет непрерывно обрабатывать Message в цикле, где сообщение получается с помощью метода Message msg =queue.next(); для получения следующего сообщения. Этот метод вызовет метод NativePollOnce(), который является собственным методом, а затем войдет в Native-уровень через вызов JNI. Механизм конвейера используется в коде Native-слоя.
  • Мы знаем, что память распределяется между потоками.Благодаря связи Handler содержимое пула сообщений не нужно копировать из одного потока в другой, поскольку память, доступная обоим потокам, представляет собой одну и ту же область, и оба имеют право на прямой доступ. Конечно, есть также темы Частная область ThreadLocal (здесь не рассматривается). Поскольку нет необходимости копировать память, какова роль конвейера?
  • Роль конвейера в механизме Handler заключается в том, что когда поток A готовит сообщение и помещает его в пул сообщений, ему необходимо уведомить другой поток B о необходимости обработки сообщения. Поток A записывает данные 1 в конец канала для записи (в старых версиях Android пишется символ W). Когда в канале есть данные, он пробуждает поток B для обработки сообщения. Основная задача конвейера — уведомить другой поток, что является основной функцией.

Зачем использовать конвейер вместо Binder?

  • С точки зрения памяти: Binder также включает копирование памяти во время процесса связи.Сообщение в механизме обработчика вообще не нужно копировать, оно находится в той же памяти. Все, что нужно обработчику, — это сообщить другому потоку, что данные доступны.
  • С точки зрения процессора базовый драйвер для связи Binder также нуждается в пуле потоков связывания.Каждая связь включает в себя создание потоков связывания и выделение памяти, что является пустой тратой ресурсов процессора.

5.3 Подробное объяснение примеров обработчиков

Handler — это интерфейс верхнего уровня механизма обмена сообщениями Android. Процесс использования Handler очень прост: можно легко переключить задачу на поток, в котором находится Handler для выполнения. Обычно сценарий использования Handler заключается в обновлении пользовательского интерфейса.

Ниже приведен простой пример использования механизма сообщений:

public class Activity extends android.app.Activity {
	private Handler mHandler = new Handler(){
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			System.out.println(msg.what);
		}
	};
	@Override
	public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
		super.onCreate(savedInstanceState, persistentState);
		setContentView(R.layout.activity_main);
		new Thread(new Runnable() {
			@Override
			public void run() {
				...............耗时操作
				Message message = Message.obtain();
				message.what = 1;
				mHandler.sendMessage(message);
			}
		}).start();
	}
}

Другой пример: как отправить сообщение из основного потока в дочерний поток? (Хотя такой сценарий применения встречается редко)

Thread thread = new Thread(){
            @Override
            public void run() {
                super.run();
                //初始化Looper,一定要写在Handler初始化之前
                Looper.prepare();
                //在子线程内部初始化handler即可,发送消息的代码可在主线程任意地方发送
                handler=new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                      //所有的事情处理完成后要退出looper,即终止Looper循环
                        //这两个方法都可以,有关这两个方法的区别自行寻找答案
                        handler.getLooper().quit();
                        handler.getLooper().quitSafely();
                    }
                };
              
                //启动Looper循环,否则Handler无法收到消息
                Looper.loop();
            }
        };
        thread.start();
    //在主线程中发送消息
    handler.sendMessage();

Давайте сначала объясним первую строку кода Looper.prepare();, сначала посмотрим на метод построения Handler.

//空参的构造方法,这个方法调用了两个参数的构造方法
  public Handler() {
        this(null, false);
    }

//两个参数的构造方法
public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  • Looper будет проверен в методе построения Handler. Если Looper пуст, будет выдано исключение нулевого указателя.
  • Handler также делает одну вещь в методе построения, указывая один из своих глобальных объектов очереди сообщений (mQueue) на очередь сообщений в Looper, то есть эту строку кода в методе построения mQueue = mLooper.mQueue;

Вторая строка кода инициализирует HandleMessage() и переопределяет метод HandleMessage(). Здесь особо нечего сказать.

Затем вызывается метод Looper.loop(), который будет объяснен позже.

Давайте сначала взглянем на основную функцию последней строки кода handler.sendMessage(message):

Давайте сначала посмотрим на поток выполнения кода после этой строки кода:

После sendMessage() код наконец выполняет метод enqueueMessage() MessageQueue с помощью нескольких методов, показанных на рисунке. Давайте посмотрим на это:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                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;
            }
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
  • MessageQueue — это односторонняя структура списка, и главное, что делает метод enqueueMessage() MessageQueue, — это вставляет сообщение, отправленное обработчиком, в список.
  • Когда вызывается метод handler.senMessage(), конечным результатом является просто вставка сообщения в очередь сообщений.

Работа по отправке сообщения завершена, когда же Looper получил сообщение? Давайте посмотрим:

public static void loop() {
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);
            } 
        }
    }
  • Это бесконечный цикл
  • Цель этого цикла — получить сообщение из MessageQueue.
  • Метод получения сообщения — метод MessageQueue.next().
  • После получения сообщения вызовите метод sendMessage() объекта message.target, чтобы распространить сообщение.
  • Условием выхода из цикла является то, что метод MessageQueue.next() возвращает значение null.

Увидев это, мы, естественно, должны подумать, что это за объект message.target.? Что делает диспетчер сообщений?

public final class Message implements Parcelable {
 /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;
}
  • После того, как Looper извлекает сообщение из MessageQueue, он вызывает метод sendMessage() обработчика.
    Здесь мы не можем не спросить, на какой обработчик указывает эта цель. Давайте посмотрим на предыдущий enqueueMessage.
//Handler的方法
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • Первая строка кода предназначена для присвоения атрибута target сообщения самому обработчику, отправляющему сообщение.
  • После того как Looper получает сообщение, он вызывает метод sendMessage() обработчика, отправившего сообщение, и передает само сообщение обратно в качестве параметра. На этом этапе логика выполнения кода вернулась к обработчику.

Затем посмотрите на метод sendMessage() обработчика.

/**
    *handler的方法
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Видите ли вы здесь метод, который нам хорошо знаком? —handleMessage() также является нашей логикой при обработке сообщений.

Наконец, вот схематическая диаграмма того, как работает система:

6. Использование обработчика (разница между Android и Java)

6.1 Обновите интерфейс пользовательского интерфейса

Использование Java:

new Thread( new Runnable() {     
    public void run() {     
         myView.invalidate();    
     }            
}).start();

Могут быть реализованы функции и обновлен интерфейс пользовательского интерфейса. Но это не работает, поскольку нарушает однопоточную модель: операции пользовательского интерфейса Android не являются потокобезопасными, и эти операции должны выполняться в потоке пользовательского интерфейса.

Thread+Handler:
обработчик обрабатывает обновления пользовательского интерфейса на основе полученных сообщений. Поток Thread отправляет сообщение Handler, чтобы уведомить пользовательский интерфейс об обновлении.

Handler myHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                    case TestHandler.GUIUPDATEIDENTIFIER:   
                         myBounceView.invalidate();  
                         break;   
               }   
               super.handleMessage(msg);   
          }   
     }; 
class myThread implements Runnable {   
          public void run() {  
               while (!Thread.currentThread().isInterrupted()) {    
                       
                    Message message = new Message();   
                    message.what = TestHandler.GUIUPDATEIDENTIFIER;   
                      
                    TestHandler.this.myHandler.sendMessage(message);   
                    try {   
                         Thread.sleep(100);    
                    } catch (InterruptedException e) {   
                         Thread.currentThread().interrupt();   
                    }   
               }   
          }   
     }   

6.2 Таймер (работа с задержкой)

Используйте Java:
используйте класс TimerTask, входящий в состав Java. TimerTask потребляет меньше ресурсов, чем Thread. Введите import java.util.Timer и импортируйте java.util.TimerTask;

public class JavaTimer extends Activity {  
  
    Timer timer = new Timer();  
    TimerTask task = new TimerTask(){   
        public void run() {  
            setTitle("hear me?");  
        }            
    };  

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
       
         timer.schedule(task, 10000);   // 延迟delay毫秒后,执行一次task。

    }  
}

TimerTask + Обработчик:

public class TestTimer extends Activity {  
  
    Timer timer = new Timer();  
    Handler handler = new Handler(){   
        public void handleMessage(Message msg) {  
            switch (msg.what) {      
            case 1:      
                setTitle("hear me?");  
                break;      
            }      
            super.handleMessage(msg);  
        }  
          
    };  

    TimerTask task = new TimerTask(){    
        public void run() {  
            Message message = new Message();      
            message.what = 1;      
            handler.sendMessage(message);    
        }            
    };  

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
    
        timer.schedule(task, 10000);   // 延迟delay毫秒后,执行一次task。
    }  
}  

6.3 Регулярно обновляйте пользовательский интерфейс

Runnable + Handler.postDelayed(runnable,time):

   private Handler handler = new Handler();  
  
    private Runnable myRunnable= new Runnable() {    
        public void run() {  
            if (run) {  
                handler.postDelayed(this, 1000);  
                count++;  
            }  
            tvCounter.setText("Count: " + count);  

        }  
    }; 

а затем позвоните ему в другом месте

handler.post(myRunnable);
handler.post(myRunnable,time);

6.4 Обработчик реализует отложенное выполнение

Обработчик задерживает 2 секунды для выполнения исполняемого файла

Handler handler=new Handler();
Runnable runnable=new Runnable() {
 @Override
	public void run() {
		// TODO Auto-generated method stub
		if(xsLayout.getVisibility()==View.VISIBLE){
			xsLayout.setVisibility(View.GONE);
		}
	}
};
handler.postDelayed(runnable, 2000);

Отмените эту запланированную задачу до выполнения исполняемого файла.

handler.removeCallbacks(runnable);

7. Резюме

7.1 Резюме

За Handler стоят Looper и MessageQueue, которые работают вместе и имеют четкое разделение труда.

  • Looper: отвечает за связанные потоки и распространение сообщений.Он получает сообщение из MessageQueue в этом потоке и передает его обработчику;
  • MessageQueue: это очередь, отвечающая за хранение и управление сообщениями, а также управление сообщениями, отправленными обработчиком;
  • Обработчик: отвечает за отправку и обработку сообщений, взаимодействие с разработчиками, предоставление API и сокрытие за ним деталей реализации.

Сообщения, отправленные обработчиком, сохраняются и управляются MessageQueue, а Loopler отвечает за обратный вызов сообщений в handleMessage().

Преобразование потоков завершается Looper, а поток, в котором находится handleMessage(), определяется потоком, в котором находится вызывающая сторона Looper.loop().

7.2 Примечание

(1) Сколько обработчиков имеет поток?

  • В процессе разработки будет создано несколько, обычно более одного обработчика.

(2) Сколько петлителей имеет нить? Как гарантировать?

  • Только 1 петлитель
  • Конструкция Looper является закрытой и может быть создана только с помощью метода подготовки(). Когда вызывается метод подготовки() Looper, будет вызываться метод get() в ThreadLocal, чтобы проверить, установлен ли Looper в ThreadLocalMap?
  • Если да, то будет выдано исключение, указывающее, что каждый поток может иметь только один Looper. Если нет, в ThreadLocalMap будет установлен новый объект Looper.
  • Это обеспечивает взаимно однозначное соответствие между ThreadLocalMap и Looper, то есть один ThreadLocalMap будет соответствовать только одному Looper. ThreadLocalMap здесь является глобальной переменной в потоке, и она будет только одна, поэтому можно гарантировать, что в потоке будет только один Looper.

(3) В чем причина утечки памяти обработчика?

  • Внутренние классы содержат ссылки на внешние объекты.
  • Принцип обработчика: поскольку обработчик может отправлять отложенные сообщения, чтобы гарантировать, что сообщение будет получено тем же обработчиком после его выполнения, отправленное сообщение будет содержать ссылку на обработчик.Эта ссылка хранится в целевом поле сообщения. и принадлежит обработчику.Метод sendMessage() в конечном итоге вызовет enqueueMessage(), а в enqueueMessage() это будет присвоено целевому полю сообщения.
  • Таким образом, сообщение содержит ссылку на обработчик, а обработчик содержит ссылку на действие. Поэтому, если действие будет уничтожено до обработки сообщения, произойдет утечка памяти.
  • Как с этим справиться? Объекты-обработчики можно изменять с помощью static.

(4) Почему основной поток может создать новый обработчик? Какие приготовления следует предпринять, если вы хотите использовать новый обработчик в дочернем потоке?

  • Поскольку функция main() в ActivityThread уже выполнила операцию подготовки() в Looper, вы можете создать новый обработчик непосредственно в основном потоке.
  • В классе SystemServer android-28:
  • Основной метод — это вход во все приложение Android. Looper.prepare() вызывается в дочернем потоке для создания объекта Looper и сохранения объекта в ThreadLocal текущего потока. Каждый поток будет иметь ThreadLocal, то есть Threads. обеспечить механизм локальной переменной копирования для достижения изоляции от других потоков. Эта переменная работает только в течение жизненного цикла этого потока, что может снизить сложность передачи общедоступной переменной между несколькими методами в одном потоке. Метод Looper.loop() предназначен для извлечения сообщения из очереди сообщений и отправки сообщения указанному обработчику с помощью метода msg.target.dispatchMassage().
    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }


    private void run() {
        ......
        Looper.prepareMainLooper();
        ......
    }
  • Если вы хотите создать новый обработчик в дочернем потоке, вам необходимо вручную вызвать метод подготовки() Looper для инициализации Looper, а затем вызвать метод Looper(), чтобы запустить Looper.
      new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();			//初始化Looper
                new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                    }
                };
                Looper.loop();
            }
        })

(5) Looper поддерживается в дочернем потоке. Каково решение, если в очереди сообщений нет сообщений?

  • Если не обработано, поток будет заблокирован.Решением является вызов функции quitSafely() Looper;
  • quitSafely() вызовет метод quit() MessageQueue, очистит все сообщения и вызовет метод ownWake(), чтобы разбудить ранее заблокированный метод ownPollOnce(), чтобы цикл for в методе next() продолжал выполняться, а затем обнаруживается, что сообщение: После нуля цикл завершится, и циклический цикл завершится. Это освобождает память и потоки.

(6) Как обеспечить внутреннюю безопасность потоков?

  • Может быть несколько обработчиков, добавляющих данные в MessageQueue (каждый обработчик может находиться в другом потоке при отправке сообщения).Метод
    добавления сообщения enqueueMessage() модифицируется с помощью синхронизации, а метод получения сообщения - следующим. (), также модифицируется с помощью синхронизации.
  • Точно ли время задержки сообщения обработчика?
    Из-за описанной выше операции блокировки невозможно гарантировать полную точность времени.

(7) Как мне создать его при использовании сообщения?

  • Созданный с использованием метода get() объекта Message, его непосредственное обновление легко вызовет дрожание памяти.
  • Дрожание памяти вызвано частыми новыми объектами и частой сборкой мусора с помощью gc, а поскольку они могут храниться в другом месте и не могут быть переработаны во времени, использование памяти будет становиться все выше и выше.
  • Использование метода get() для повторного использования памяти позволяет избежать дрожания памяти. Он поддерживает внутренний пул сообщений, который представляет собой структуру связанного списка.При вызове метода get() сообщение в заголовке таблицы будет повторно использоваться, а затем указывать на следующее. Если в заголовке нет повторно используемого сообщения, будет создан новый объект. Максимальная длина этого пула объектов — 50.

(8) Что произойдет с очередью сообщений после использования postDelay в Handler?

  • Если в это время очередь сообщений пуста, оно не будет выполнено. Будет рассчитано время ожидания сообщения, и выполнение продолжится, когда время ожидания истечет.

(9) Почему бесконечный цикл Looper не приводит к зависанию приложения?

  • Завис ANR, есть две причины:
  • 1. Нет реакции на события ввода (например, нажатия клавиш, прикосновения и т. д.) в течение 5 секунд.
  • 2. BroadcastReceiver не завершил выполнение в течение 10 с.
  • Фактически, все наши Действия и Службы выполняются в функции цикла() и существуют в виде сообщений, поэтому, когда сообщения не создаются, циклический цикл будет заблокирован (заблокирован), а основной поток перейдет в спящий режим. событие ввода. Или основной поток будет разбужен после того, как Looper добавит сообщение в ответ на событие, поэтому это не вызовет ANR.
  • Проще говоря, блокировка петлителя указывает на отсутствие ввода события, а ANR вызвано тем, что событие не отвечает, поэтому бесконечный цикл петлителя не приведет к зависанию приложения.

6.3 Далее

Введение

В системе Android после выполнения трудоемких операций мы должны запустить для выполнения еще один подпоток.После выполнения потока поток будет автоматически уничтожен. Представьте себе, если мы часто выполняем трудоемкие операции в проекте, если мы часто запускаем потоки, а затем уничтожаем их, это, несомненно, будет потреблять много производительности.

HandlerThread инкапсулирован Google и может использоваться для выполнения множества трудоемких операций без многократного запуска потока. Он реализован с помощью Handler и Looper.

HanderThread на самом деле является потоком

как использовать?

//创建实例对象,该参数表示线程的名字
HandlerThread handlerThread = new HandlerThread("myHandlerThread");

//启动我们创建的HandlerThread线程
handlerThread.start();

//怎样将Handler与线程对象绑定在一起
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
	@Override
	public void handleMessage(Message msg) {
		//发生myHandlerThread线程中
		checkForUpdate();
		if(isUpdate){
			mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
		}
	}
};

Посмотрите пример

public class MainActivity extends AppCompatActivity {
	private static final int MSG_UPDATE_INFO = 0x100;
	Handler mMainHandler = new Handler();
	private TextView mTv;
	private Handler mThreadHandler;
	private HandlerThread mHandlerThread;
	private boolean isUpdate = true;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mTv = (TextView) findViewById(R.id.tv);
		initHandlerThread();
	}
	private void initHandlerThread() {
		mHandlerThread = new HandlerThread("xujun");
		mHandlerThread.start();
		mThreadHandler = new Handler(mHandlerThread.getLooper()){
			@Override
			public void handleMessage(Message msg) {
				checkForUpdate();
				if (isUpdate) {
					mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
				}
			}
		};
	}
	
/**
* 模拟从服务器解析数据
*/
	private void checkForUpdate() {
		try {
			//模拟耗时
			Thread.sleep(1200);
			mMainHandler.post(new Runnable() {
				@Override
				public void run() {
					String result = "实时更新中,当前股票行情:<fontcolor='red'>%d</font>";
					result = String.format(result, (int) (Math.random() * 5000 + 1000));
					mTv.setText(Html.fromHtml(result));
				}	
			});
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected void onResume() {
		isUpdate = true;
		super.onResume();
		mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
	}
	@Override
	protected void onPause() {
		super.onPause();
		isUpdate = false;
		mThreadHandler.removeMessages(MSG_UPDATE_INFO);
	}
@Override
	protected void onDestroy() {
		super.onDestroy();
		mHandlerThread.quit();
		mMainHandler.removeCallbacksAndMessages(null);
	}
}

Запустите приведенный выше тестовый код, и вы увидите следующие рендеры (пример не подходит, основной сценарий использования — выполнение трудоемких операций в handleMessage)

Посмотрите исходный код

Судя по sdk23, видно, что строк кода всего больше ста:

public class HandlerThread extends Thread {
	int mPriority;
	int mTid = -1;
	Looper mLooper;
	public HandlerThread(String name) {
		super(name);
		mPriority = Process.THREAD_PRIORITY_DEFAULT;
	}
	public HandlerThread(String name, int priority) {
		super(name);
		mPriority = priority;
	}
/**
* Call back method that can be explicitly overridden if nee
ded to execute some
* setup before Looper loops.
*/
	protected void onLooperPrepared() {
	}
	
	@Override
	public void run() {
		mTid = Process.myTid();
		Looper.prepare();
		//持有锁机制来获得当前线程的Looper对象
		synchronized (this) {
			mLooper = Looper.myLooper();
			//发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
			notifyAll();
		}
		//设置线程的优先级别
		Process.setThreadPriority(mPriority);
		//这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
		onLooperPrepared();
		Looper.loop();
		mTid = -1;
	}
	
	public Looper getLooper() {
		if (!isAlive()) {
		return null;
		}
		// 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
		synchronized (this) {
			while (isAlive() && mLooper == null) {
				try {
					wait();
				} catch (InterruptedException e) {
				}
			}
		}
		return mLooper;
	}
	
	public boolean quit() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quit();
			return true;
		}
		return false;
	}
	
	public boolean quitSafely() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quitSafely();
			return true;
		}
		return false;
	}
/**
* Returns the identifier of this thread. See Process.myTid(
).
*/
	public int getThreadId() {
		return mTid;
	}
}

(1) Сначала посмотрим на конструктор

	public HandlerThread(String name) {
		super(name);
		mPriority = Process.THREAD_PRIORITY_DEFAULT;
	}
	public HandlerThread(String name, int priority) {
		super(name);
		mPriority = priority;
	}

Один параметр и два параметра, имя представляет имя текущего потока, приоритет — это уровень приоритета потока.

(2) Взгляните на метод run().
В методе run мы видим, что мы инициализируем Looper и устанавливаем уровень приоритета потока.

	public void run() {
		mTid = Process.myTid();
		Looper.prepare();
		//持有锁机制来获得当前线程的Looper对象
		synchronized (this) {
			mLooper = Looper.myLooper();
			//发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
			notifyAll();
		}
		//设置线程的优先级别
		Process.setThreadPriority(mPriority);
		//这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
		onLooperPrepared();
		Looper.loop();
		mTid = -1;
	}

Ранее мы упоминали, что при использовании HandlerThread мы должны вызвать метод start(), а затем мы можем связать наш HandlerThread и наш обработчик вместе.

  • Причина в том, что мы начинаем инициализировать наш циклер только в методе run(), и когда мы вызываем метод start() класса HandlerThread, поток будет передан виртуальной машине для планирования, и виртуальная машина автоматически вызовет метод запуска.

Зачем использовать механизм блокировки и notifyAll():

  • Мы можем узнать причину из метода getLooper().
	public Looper getLooper() {
		if (!isAlive()) {
			return null;
		}
	// 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
	synchronized (this) {
		while (isAlive() && mLooper == null) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
	}
	return mLooper;
}

Сводка: При получении объекта mLooper возникла проблема синхронизации.Значение mLooper можно получить только в том случае, если поток успешно создан и объект Looper также успешно создан. Здесь метод ожидания wait и метод notifyAll в методе run совместно решают проблему синхронизации.

(3) Далее давайте посмотрим на метод quit и метод quitSafe.

	public boolean quit() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quit();
			return true;
		}
		return false;
	}
	
	public boolean quitSafely() {
		Looper looper = getLooper();
		if (looper != null) {
			looper.quitSafely();
			return true;
		}
		return false;
	}

Эти два метода легко отследить и знать, что только два метода в конечном итоге вызовут метод quit (логический безопасный) MessageQueue.

void quit(boolean safe) {
	if (!mQuitAllowed) {
		throw new IllegalStateException("Main thread not allowedto quit.");
	}
	synchronized (this) {
		if (mQuitting) {
			return;
		}
		mQuitting = true;
		//安全退出调用这个方法
		if (safe) {
			removeAllFutureMessagesLocked();
		} else {//不安全退出调用这个方法
			removeAllMessagesLocked();
		}
		// We can assume mPtr != 0 because mQuitting was previously false.
		nativeWake(mPtr);
	}
}

Если это небезопасно, будет вызван метод removeAllMessagesLocked();. Давайте посмотрим, как обрабатывается этот метод. Он фактически проходит по связанному списку сообщений, удаляет обратные вызовы всех сообщений и сбрасывает их в нулевые значения.

private void removeAllMessagesLocked() {
	Message p = mMessages;
	while (p != null) {
		Message n = p.next;
		p.recycleUnchecked();
		p = n;
	}
	mMessages = null;
}

Безопасно вызывайте метод removeAllFutureMessagesLocked(); этот метод определит, обрабатывает ли наша текущая очередь сообщений сообщения на основе свойства Message.when. Если сообщения не обрабатываются, все обратные вызовы будут удалены напрямую. Если да, дождитесь, пока сообщение будет обработано. обработано. Выход из цикла после обработки. Таким образом, quitSafe() безопасен, но метод quit() небезопасен, поскольку метод quit напрямую удаляет все обратные вызовы независимо от того, обрабатывается ли сообщение.

private void removeAllFutureMessagesLocked() {
	final long now = SystemClock.uptimeMillis();
	Message p = mMessages;
	if (p != null) {
		//判断当前队列中的消息是否正在处理这个消息,没有的话,直接移除所有回调
		if (p.when > now) {
			removeAllMessagesLocked();
		} else {
			//正在处理的话,等待该消息处理处理完毕再退出该循环
			Message n;
			for (;;) {
				n = p.next;
				if (n == null) {
					return;
				}
				if (n.when > now) {
					break;
				}
				p = n;
			}
			p.next = null;
			do {
				p = n;
				n = p.next;
				p.recycleUnchecked();
			} while (n != null);
		}
	}
}

Guess you like

Origin blog.csdn.net/qq_32907491/article/details/132645806