子线程通知更新UI的几种方式总结,相关类的总结与使用案例

2. 子线程中更新UI

使用子线程之后,随之而来的是对UI更新的问题。在Android是不允许在子线程中进行UI更新的。但是有些时候,我们必须在子线程执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件。对于这种情况,Android提供了一套异步消息处理机制。所以我们必须了解如何进行异步消息通信。

2.1 异步消息处理机制

异步消息处理主要由四个部分组成:MessageHanlderMessageQueueLooper
所涉及到的全部元素:ThreadHandlerLooperMessageQueueThreadLocalThreadLocalMap

  • Thread:消息机制的作用域;
  • Handler:负责开启和结束消息机制,主要用于发送和处理消息;
  • Looper:负责管理消息队列,接受Handler传送过来的消息并将队列中的消息传递给Handler
  • Message:为线程之间传递的消息。
  • MessageQueue:消息队列,相当于消息管道,内部使用一个Message链表实现消息的存和取。
  • ThreadLocal:为线程提供数据存储功能,所存储的数据只属于该线程;
  • ThreadLocalMap:为线程提供数据存储功能的具体集合;

Handler源码阅读笔记(一)Handler.sendMessage()与 Handler.post() & 子线程更新UI中已经粗浅的介绍了这四个部分。这里就不再继续复述。
工作过程如下:
在这里插入图片描述

  • Handle 通过 sendMessage() 等方法发送一个消息,调用 MessageQueueenqueueMessage 方法 将消息添加到消息队列中;
  • Looperloop方法发现新消息后,从队列中取出消息,最后将其转发到 Handle 中,最终在 handleMessage 进行处理。
  • Looper 是运行在创建handler 的线程中,这样将Handler 中的业务逻辑切换到 穿件 Handler 的线程中去了。

2.1.1 经典问题1:ActivityThread的main方法

我们知道,app运行时通过Zygote fork来创建一个进程,用于承载App上运行的各种组件。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性来指定Activity等的运行进程。

我们知道Android程序的入口在ActivityThread类中的main方法中,那么不妨粗略的看下这个方法:

public static void main(String[] args) {
    Looper.prepareMainLooper();

    ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    ...
    
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

对于prepareMainLooper方法:

// Looper.java
public static void prepareMainLooper() {
    prepare(false);  // quitAllowed = false
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

// 构造方法
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

ActivityThread类中创建Looper对象的时候,创建的是一个不允许退出的Looper对象。默认的Looper.prepare()方法默认是允许退出的。

Looper.prepare()中会调用Looper的构造方法来创建Looper对象示例,在其构造方法中,我们可以看见这里初始化了一个消息队列MessageQueue

Looper底层使用ThreadLocal<Looper>来进行存储。不妨看下这个ThreadLocal类的一些方法:

// ThreadLocal.java
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocalMapThreadLocal的一个内部类,类似于HashMap。且继承了WeakReference弱引用。从t.threadLocals可以知道,每个线程类内部都关联了一个ThreadLocalMap对象,在这个Map对象中我们可以通过ThreadLocal对象得到存储的目标对象,也即是Looper对象。

同时对于main方法中的sMainThreadHandler = thread.getHandler();方法,这里的Hanlder是在定义的时候直接初始化的:

// ActivityThread.java
final H mH = new H();
class H extends Handler
final Handler getHandler() {
    return mH;
}

故而也就是在程序载入的时候,就直接初始化了Hanlder示例。也就是说到目前为止,在主线程中就已经穿个件好了Looper实例,而Looper在初始化的时候会初始化对应的MessageQueueThreadLocalThreadLocalMap对象,并且每个线程存在ThreadLocalMap对象,故而直接通过当前线程对象,得到ThreadLocalMap对象,然后从该对象中获取到初始化设置的Looper对象。

也就是在main方法中,就初始化了消息机制中的各个对象。

并且在最后使用Looper.loop();方法来开启了一个死循环,以保证app不会退出。

2.1.2 经典问题2:Looper.loop();方法为什么不会导致循环卡死?

在该方法中,我们可以看到:

public static void loop() {
	...
	for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
             return;
        }
		...
	}
}

在这个死循环中使用了MessageQueuenext()方法,并且当消息队列中没有消息的时候,该方法可能会阻塞。而next()方法的实现如下:

Message next() {
	...
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);
    	...
    }
}

保证其不会卡死的关键就在于nativePollOnce方法,这个方法在Java中是一个native方法:

private native void nativePollOnce(long ptr, int timeoutMillis);

因为使用了Linux下的pipeepoll机制。nativePollOnce()方法,这便是一个native方法,再通过JNI调用进入Native层,在Native层的代码中便采用了管道机制。管道的一端的读,另一端写,标准的生产者消费者模式。

Handler机制中管道作用就是当一个线程A准备好Message,并放入消息池,这时需要通知另一个线程B去处理这个消息。线程A向管道的写端写入数据,管道有数据便会唤醒线程B去处理消息。管道主要工作是用于通知另一个线程的,这便是最核心的作用。

简单说就是在主线程的MessageQueue没有消息时,便阻塞在loopqueue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

上段归纳内容来源:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

2.2 AsyncTask

通过上面的部分,我们知道了异步消息处理机制的一些常识。在Android中还提供了一种非常简单的方式来从子线程切换到主线程。用来作为替代Thread + Handler的辅助类。AsyncTask可以很轻松地执行异步任务并更新ui,但由于context泄露,回调遗漏,configuration变化导致崩溃等原因,在api 30(Android 11)AsyncTask被正式废弃。而建议使用:

  • java.util.concurrent包下的相关类;
  • 协程;

当然,AsyncTask背后的实现原理也是基于异步消息机制,且底层使用线程池来实现线程的复用,帮我们做了一些封装。AsyncTask类是一个抽象类,我们如果需要使用就需要实现其子类。

在继承这个类的时候,需要指定三个泛型参数,比如:

class MyAsyncTask extends AsyncTask<Params, Progress, Result>

在这里插入图片描述

  • 泛型Params表示执行时候需要传入的参数,比如Http请求的Uri。如果不需要可指定为Void
  • 泛型Progress表示后台执行的百分比;
  • 泛型Result表示后台执行任务最终返回的结果,比如String等。

比如:

class MyAsyncTask extends AsyncTask<Void, Integer, String>

一共提供了下面几个方法:
在这里插入图片描述
因为context泄露、配置发生改变的崩溃等原因,注意到这里已经不推荐使用了。

public class MyAsyncTask extends AsyncTask<Void, Integer, String> {

    @Override
    protected void onPreExecute() {
        // 任务执行前,在UI线程中执行
        super.onPreExecute();
    }

    @Override
    protected void onPostExecute(String s) {
        // 任务执行完毕,在UI线程中执行
        super.onPostExecute(s);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // 该方法中可以对UI进行更新,在UI线程中执行
        super.onProgressUpdate(values);
    }

    @Override
    protected String doInBackground(Void... voids) {
        // 在子线程中运行,可以做耗时操作。不可进行UI操作,
        // 如果需要更新UI操作,可以调用 publishProgress(); 方法
        // 会去调用onProgressUpdate方法。
        return null;
    }
}

需要注意到一点,那么就是只有doInBackground方法才是在子线程中调用,可以在这个方法中做耗时操作,其他方法不可,且不要在doInBackground方法进行UI更新。

比如:

public class MyAsyncTask extends AsyncTask<Void, Integer, String> {
    private ProgressBar mBar;
    public MyAsyncTask(ProgressBar bar){
        this.mBar = bar;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        // 该方法中可以对UI进行更新
        mBar.setProgress(values[0]);
    }

    @Override
    protected String doInBackground(Void... voids) {
        // 所有代码在子线程中运行,不可进行UI操作;
        // 如果需要更新UI操作,可以调用 publishProgress(); 方法。它会去调用onProgressUpdate方法。
        int count = 0;
        while(count < 100){
            count+=5;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            publishProgress(count);
        }
        return null;
    }
}

Activity

public class MainActivity extends AppCompatActivity {
    private ProgressBar bar;
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bar = findViewById(R.id.id_bar);
        button = findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyAsyncTask task = new MyAsyncTask(bar);
                task.execute();
            }
        });
    }
}

在这里插入图片描述

2.3 Activity类中提供了runOnUiThread(Runnable action)方法

比如上面的案例可以改写为:

public class MainActivity extends AppCompatActivity {

    private ProgressBar bar;
    private Button button;
    private int count = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bar = findViewById(R.id.id_bar);
        button = findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while(count < 100){
                            count+=5;
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            runOnUiThread(()->{bar.setProgress(count);});
                        }
                    }
                }).start();
            }
        });
    }
}

我们追踪下这个方法:

// Activity.java
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

底部调用的handlerpost方法,也就是说我们可以使用handlerpost方法来进行完成任务。

2.4 Handler方式

sendMessage

public class MainActivity extends AppCompatActivity {

    private ProgressBar bar;
    private Button button;
    private int count = 0;
    private Handler mhandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bar = findViewById(R.id.id_bar);
        button = findViewById(R.id.button);
        mhandler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                bar.setProgress(msg.arg1);
            }
        };

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while(count < 100){
                            count+=5;
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            Message msg = new Message();
                            msg.arg1 = count;
                            mhandler.sendMessage(msg);
                        }
                    }
                }).start();
            }
        });
    }
}

handler.post

public class MainActivity extends AppCompatActivity {

    private ProgressBar bar;
    private Button button;
    private int count = 0;
    private Handler mhandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bar = findViewById(R.id.id_bar);
        button = findViewById(R.id.button);
        mhandler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                bar.setProgress(msg.arg1);
            }
        };

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while(count < 100){
                            count+=5;
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            mhandler.post(()->{bar.setProgress(count);});
                        }
                        Looper.loop();
                    }
                }).start();
            }
        });
    }
}

2.5 View的post方法

直接替换mhandler.post(()->{bar.setProgress(count);});代码为:

bar.post(()->{bar.setProgress(count);});

即可达到一样的效果。

解读:

// Activity.java
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

// View.java
public boolean post(Runnable action) {
   final AttachInfo attachInfo = mAttachInfo;
   if (attachInfo != null) {
       return attachInfo.mHandler.post(action);
   }
   getRunQueue().post(action);
   return true;
}

// Handler.java
public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);  // enqueueMessage(queue, msg, uptimeMillis);
}

不难发现这几个方法的底层最后都是使用到了Handler的消息机制。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦否

文章对你有用?不妨打赏一毛两毛

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值