- 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