Android高级开发工程师-Handler Message学习

1,436 阅读4分钟
  • Handler内存泄漏测试
  • 为什么不能在子线程创建Handler
  • TextView setText()是不是不能在子线程执行
  • new Handler()两种方式有什么区别
  • ThreadLocal用法与原理

一、Handler内存泄漏测试

1.测试代码

public class MainActivity extends AppCompatActivity {
    /**
     * 定义Handler
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                Toast.makeText(MainActivity.this, "handleMessage", Toast.LENGTH_SHORT).show();
            }
        }
    };
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            // 模拟延迟
            SystemClock.sleep(2000);

            // 模拟延迟回调处理
            Message msg = new Message();
            msg.what = 1;
            mHandler.sendMessage(msg);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                /**
                 * 模拟异步请求
                 */
                new Thread(mRunnable).start();
                // 模拟关闭页面
                finish();
            }
        });
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
    }
}

2.执行结果

点击FloatingActionButton
1. 页面关闭,Toast展示onDestroy。
2. 2秒后,Toast展示handleMessage。

3.结论

说明MainActivity关闭后,虽然执行了onDestroy方法,但并没有被回收。

4.分析

  • Activity关闭后,线程Thread仍然在执行。
  • Toast展示完handleMessage后,通过Profile观察内存情况,发现已经没有MainActivity存在,说明线程和Handler执行完后,内存会释放,不会导致内存泄漏。
  • 这种情况在onDestroy()使用Handler.removeCallbacks(null)是无法解决的,因为执行该方法时,还没有往Handler中添加Message。这种方式适用于使用Handler.postDelayed()的方式进行延迟处理。
  • 详细参见 juejin.cn/post/684490…

二、为什么不能在子线程创建Handler

1.测试代码

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Handler();
            }
        }).start();
    }
}

2.执行结果

在google手机,执行,抛出异常
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:207)
        at android.os.Handler.<init>(Handler.java:119)
        at com.example.outman.handlersample.MainActivity$2.run(MainActivity.java:41)
        at java.lang.Thread.run(Thread.java:919)

3.分析

查看new Handler()源码

public Handler(Callback callback, boolean async) {
        ....
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
/**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

4.结论

可见,从ThreadLocal中通过当前线程获取Looper,因为在子线程中,没有创建Looper,所以获取为null,所以抛出异常。

5.扩展,为什么在主线程,可以直接创建

我们知道主线程是ActivityTread

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

        Looper.prepareMainLooper();

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

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

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

可见,通过执行Looper.prepareMainLooper()已经创建了Looper对象。

6.如何在子线程创建Handler

通过分析主线程可知,只需要先创建Looper即可。

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                new Handler();
                Looper.loop();
            }
        }).start();

三、TextView setText()是不是不能在子线程执行

1.测试代码

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

        final TextView txt = findViewById(R.id.txt);
        new Thread(new Runnable() {
            @Override
            public void run() {
                txt.setText("Hello Handler");
            }
        }).start();
    }
}

2.运行结果

没有发生异常

3.测试代码

在线程延迟500毫秒

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

        final TextView txt = findViewById(R.id.txt);
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(500);
                txt.setText("Hello Handler");
            }
        }).start();
    }
}

4.运行结果

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
        at android.view.View.requestLayout(View.java:24469)
        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
        at android.view.View.requestLayout(View.java:24469)
        at android.widget.TextView.checkForRelayout(TextView.java:9681)
        at android.widget.TextView.setText(TextView.java:6269)
        at android.widget.TextView.setText(TextView.java:6097)
        at android.widget.TextView.setText(TextView.java:6049)

5.分析

  • 可见,异常发生在ViewRootImpl requestLayout()方法中
  • 在TextView的setText()方法中,调用的checkForRelayout()导致,调用有个条件就是,mLayout != null。
  • 是不是2次测试代码,mLayout的值不一样,第一次没有发生异常是因为mLayout为null导致没有执行调用的checkForRelayout()呢?

6.验证代码

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

        final TextView txt = findViewById(R.id.txt);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e("Handler", "~~~~~~~~~" + txt.getLayout());
                SystemClock.sleep(500);
                Log.e("Handler", "~~~~~~~~~" + txt.getLayout());
                txt.setText("Hello Handler");
            }
        }).start();
    }
}

7.执行结果

E/Handler: ~~~~~~~~~null
E/Handler: ~~~~~~~~~android.text.BoringLayout@9a97cf9

8.结论

  • 可见,直接原因就是TextView中mLayout对象的创建问题导致
  • 根本原因,要从Android的View的创建和绘制说起。参考https://juejin.cn/post/6844903454461132813

四、new Handler()两种方式

    private Handler mHandler1 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    });
    private Handler mHandler2 = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

我们知道,Handler发消息,是放在MessageQueue里,那么是如何获取消息的呢?

通过Looper.loop()方法获取消息,再通过Handler dispatchMessage分发消息。

        try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
/**
     * 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);
        }
    }

结论

可见,先执行mCallback.handleMessage(),如果返回false,则再执行handleMessage()方法。

ThreadLocal用法与原理

1.源码

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    protected T initialValue() {
        return null;
    }
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal中是通过ThreadLocalMap来保存数据的,Key为线程,Value为T对象。如果获取不到,则调用初始化方法()。

2.测试代码

public class MainActivity extends AppCompatActivity {
    private ThreadLocal<String> mThreadLocal = new ThreadLocal<String>() {
        @Nullable
        @Override
        protected String initialValue() {
            return "Init ThreadLocal";
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e("ThreadLocal", "thread " + Thread.currentThread() + " " + mThreadLocal.get());
                mThreadLocal.set("Test1");

                SystemClock.sleep(2000);

                Log.e("ThreadLocal", "thread " + Thread.currentThread() + " " + mThreadLocal.get());
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e("ThreadLocal", "thread " + Thread.currentThread() + " " + mThreadLocal.get());
                mThreadLocal.set("Test2");
                Log.e("ThreadLocal", "thread " + Thread.currentThread() + " " + mThreadLocal.get());
            }
        }).start();
    }
}

3.执行结果

E/ThreadLocal: thread Thread[Thread-2,5,main] Init ThreadLocal
E/ThreadLocal: thread Thread[Thread-3,5,main] Init ThreadLocal
E/ThreadLocal: thread Thread[Thread-3,5,main] Test2
E/ThreadLocal: thread Thread[Thread-2,5,main] Test1