子线程频繁更新ui导致界面卡顿问题?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了子线程频繁更新ui导致界面卡顿问题?相关的知识,希望对你有一定的参考价值。

我是在子线程更新UI,所以使用的是runOnUiThread接口来更新TextView。界面是个安装应用显示安装进度的效果(一个文本框显示数据,一个滚动条实时滑动到最底部)。由于上万次的调用printData()方法,导致界面卡顿,最后程序执行完成,我的界面要等1分多钟才显示完成,请问应该怎么解决。

你这明明是在主线程更新的UI显示,runOnUIThread方法就是运行在主线程的,你可以尝试使用handle发送来更改文本或使用livedata或者asyncTask(虽然现在已经被废弃) 参考技术A 上万次的调用printData()方法。启动了上万线程?再加上回收的时间。用线程池试试吧。会不会好点。追问

以前没用过线程池,有示例代码吗?

追答

val diskIO: Executor = Executors.newFixedThreadPool(3),
diskIO.execute(Runnable
)
自己转java代码

本回答被提问者采纳
参考技术B 没看懂这么做有什么意义,几秒钟不停滚屏然后看了个寂寞?追问

这只是一个程序片段,只是问题定位在哪里,需要解决。

Android消息机制

概述

作用

为什么不能在主线程中进行耗时操作?
因为会导致体验不流畅、卡顿,体验差。
Handler的出现为了解决在子线程中操作完成了无法访问UI线程(主线程)的矛盾。
为什么不能在子线程中更新UI?
因为多线程操作导致线程不同步,并发操作让UI控件处于不可预期的状态。
为什么不通过线程同步更新UI?通过给UI控件的访问加上锁
1. 加锁会让UI访问逻辑变得异常复杂
2. 锁阻塞某些线程的执行,降低UI访问效率

ThreadLocal

为什么要使用ThreadLocal

ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.
ThreadLocal是一个线程内部数据存储类,当某些数据是以线程为作用域,并且在不同线程是具有不同的数据副本的时候,就可以考虑使用它。例如:在一个复杂的使用场景,比如线程内,一个监听器对象需要贯穿整个线程的执行,在不使用ThreadLocal的情况下解决思路:
1. 把监听器对象 作为函数的一个参数,在函数之间传递。如果当函数调用栈很深的时候,这样的设计看起来会特别糟糕。
2. 把监听器作为一个静态变量。在单一线程中执行,这是没有问题的,但是如果是多个线程呢,那么我们需要为不同线程创建一个不同的监听器,如果是10个线程就是10个静态监听器,显然这也不是一个良好的设计。

在Handler的使用,需要获得当前线程的Looper,很显然Looper的作用域是当前线程,并且不同的线程具备不同的Looper,使用Handler就可以轻松读取。

怎么使用ThreadLocal

定义一个储存Integer类型的ThreadLocal对象,在分别找主线程,子线程1,子线程2中修改和访问它的值

    private ThreadLocal<Integer> mThreadLocal = new ThreadLocal<Integer>();

    private void testThreadLocal() {
        ExecutorService ePool = Executors.newFixedThreadPool(2);
        mThreadLocal.set(0);
        Log.d("TAG", "ThreadMain:" + mThreadLocal.get());
        ePool.execute(new Thread("Thread1") {
            @Override
            public void run() {
                super.run();
                mThreadLocal.set(1);
                Log.d("TAG", "Thread1:" + mThreadLocal.get());
            }
        });

        ePool.execute(new Thread("Thread2") {
            @Override
            public void run() {
                super.run();
                Log.d("TAG", "Thread2:" + mThreadLocal.get());
            }
        });
    }

结果:

05-31 16:33:30.698: D/TAG(11113): ThreadMain:0
05-31 16:33:30.698: D/TAG(11113): Thread2:null
05-31 16:33:30.698: D/TAG(11113): Thread1:1

因为子线程2中没有设置过mThreadLocal的值,所以返回了一个空对象。可以看到在不同线程中,获得的mThreadLocal对象值是不一样的。

怎么实现ThreadLocal

如果让我们自己来实现如何实现?

在Thread中创建一个Map命名为threadLocals,利用当前运行的Thread作为Key,存储对象Object为Value,在不考虑null处理、泛型、初始化的时候实现方式。

public class ThreadLocal {
    private Map getMap() {
        return Thread.currentThread().threadLocals;
    }
    public Object get() {
        return getMap().get(this);
    }
    public void set(Object value) {
        getMap.put(this, value);
    }
}

源码实现方式

set( )方法

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

initialValue()初始化方法

    protected T initialValue() {
        return null;
    }

get()方法

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

发现其实系统实现的方式和我们想的差不多,只是Values这个类扮演了我们自己实现方式的threadLocals用来具体存储数据。

  if (values == null) {
            values = initializeValues(currentThread);
        }

如果存储具体对象values没有初值,就使用初始化方法initializeValues()实例化它。例如将ThreadLocal使用的例子初始化改为

    private ThreadLocal<Integer> mThreadLocal = new ThreadLocal<Integer>() {

        @Override
        protected Integer initialValue() {
            return 100;
        }
    };

Thread2调用

        ePool.execute(new Thread("Thread2") {
            @Override
            public void run() {
                super.run();
                Log.d("TAG", "Thread2:" + mThreadLocal.get());
            }
        });

因为ThreadLocal没有初始化,直接调用initializeValues()方法得到:

05-31 16:33:30.698: D/TAG(11113): Thread2:100

Looper

使用场景

在消息机制里,每个Thread都需要有一个独立的Looper对象,通过ThreadLocal实现。现在来看看一个Looper结合Handler的典型使用场景:

    class LooperThread extends Thread {
        public Handler mHandler;

        public void run() {
            Looper.prepare();

            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
            Looper.loop();
        }
    }

一个具备消息机制的线程实现,大致需要3个步骤:

  1. Looper.prepare(); Looper准备
  2. new Handler,同时处理消息
  3. Looper.loop();Looper循环
    查看源码他们的实现:

Looper.prepare()

    public static void prepare() {
        prepare(true);
    }

    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));
    }

prepare(boolean quitAllowed)方法调用的开始会判断sThreadLocal.get() != null,这说明prepare()方法在同一个线程中不能调用多次, 也不能重写ThreadLocal的initializeValues()方法,创建默认Looper。然后把Looper设置到sThreadLocal,根据sThreadLocal的特性可以保证他在独立线程中的唯一性。

Looper.loop()

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            msg.target.dispatchMessage(msg);
            ...省略部分代码
            msg.recycle();
        }
    }

    public static Looper myLooper() {
        return sThreadLocal.get();
    }
  1. 先去通过myLooper()获取到Looper,判断是否为空,如果为空证明Looper.prepare()没有调用,抛出异常。
  2. for (;;) {}实现一个死循环,让Looper循环执行。
  3. me.mQueue拿到消息队列MessageQueue,然后通过queue.next()方法取出消息,这是个阻塞方法,如果消息消息队列就阻塞在这里,等待消息。
  4. 如果拿到消息就通过msg.target.dispatchMessage(msg);处理消息,这就回到了当前线程,这里的msg.target其实就是当前线程的Handler
  5. msg.recycle()回收消息

MessageQueue

next()

    Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // We can assume mPtr != 0 because the loop is obviously still
            // running.
            // The looper will not call this method after the loop quits.
            nativePollOnce(mPtr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier. Find the next asynchronous message
                    // in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready. Set a timeout to wake up
                        // when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now,
                                Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false)
                            Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have
                // been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first
                // message
                // in the queue (possibly a barrier) is due to be handled in the
                // future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(
                            pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers
                        .toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the
                                                // handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been
            // delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
  1. 在Looper里面提到Looper.loop()中通过MessageQueue的next()方法获取Message和实现阻塞。
  2. MessageQueue的next方法中,也是通过无限循环实现获取消息,private native void nativePollOnce(mPtr, nextPollTimeoutMillis);通过这个native方法,实现消息获取。当没有消息时会令nextPollTimeoutMillis为-1,调用方法么,实现了阻塞。
  3. MessageQueue虽然取名为Queue但是实际上,并不是Queue(队列)而是通过单链表来实现。这一点在插入消息的方法enqueueMessage()中可以更加清晰的看出

enqueueMessage()

    boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }

        synchronized (this) {
            if (mQuitting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

            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 {
                // Inserted within the middle of the queue.  Usually we don‘t have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
    }

Handler

send()、post()

Handler通过sendMessageAtTime()来发送消息,他具体是根据MessageQueue的enqueueMessage()方法来实现。

    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);
    }

封装实现sendMessageDelayed()

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

postDelayed() post() postAtTime()一系列方法也是通过sendMessage()方法封装实现

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

dispatchMessage()

    /**
     * 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);
        }
    }

执行步骤:
1. 先判断msg的callback是否为空,不为空就直接调用他的handleCallback()方法。

    private static void handleCallback(Message message) {
        message.callback.run();
    }

Message的callback方法可以通过他的obtain()方法设置

    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;

        return m;
    }

2.如果 msg.callback == null ,判断mCallback是否为空,不为空的话,执行mCallback.handleMessage(msg)

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

看到mCallback其实是接口Callback的实例,这个接口的实现,也就是使用中最常用的用法

        Handler handler = new Handler(new Callback() {

            @Override
            public boolean handleMessage(Message msg) {
                // TODO Auto-generated method stub
                return false;
            }
        });

应该很熟悉吧,这是一种巧妙的实现方式,设想如果没有这个接口实现的方式。那么就要通过派生Handler的一个子类,然后重写他的handleMessage()方法,而把Callback作为构造函数的一个参数传递给匿名内部类,省去了很多麻烦。

3.最后执行handleMessage(msg)

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

Callback 接口中的同名函数handleMessage()
执行流程:

Created with Rapha?l 2.1.0Startmsg.callback == null mCallback != nullhandleMessage(msg)EndhandleMessage(msg)handleCallbackyesnoyesnoyesno

主线程的消息循环

Android的主线程就是ActivityThread,主线程的入口函数和Java程序的入口函数一样是public static void main(String[] args)


    public static void main(String[] args) {  
         //......省略部分代码

        Looper.prepareMainLooper();  

        // 创建ActivityThread实例  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  

        if (sMainThreadHandler == null) {  
            sMainThreadHandler = thread.getHandler();  
        }  

        AsyncTask.init();  

        if (false) {  
            Looper.myLooper().setMessageLogging(new  
                    LogPrinter(Log.DEBUG, "ActivityThread"));  
        }  

        Looper.loop();  

        throw new RuntimeException("Main thread loop unexpectedly exited");  
    } 

同样,主线程的Looper Handler 和MessageQueue开启流程和在Thead中开启大体一致,使用的是prepareMainLooper(),查看代码

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

发现prepareMainLooper()其实就是调用的prepare(false);实现,只是多了一个主线程中的Looper初始化赋值的步骤sMainLooper = myLooper()
当然在Looper中也存在退出的操作

  public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }

这里quit()是直接退出,quitSafely()等待队列任务执行完成后退出

以上是关于子线程频繁更新ui导致界面卡顿问题?的主要内容,如果未能解决你的问题,请参考以下文章

Android消息机制

C++程序卡死UI界面卡顿问题的原因分析与总结

C++程序卡死UI界面卡顿问题的原因分析与总结

ios开发在子线程更新ui会怎样

为啥loop之后就可以子线程更新ui

Runloop线程常驻