MainActivity
package com.example.handler_sample;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public static final int MSG_CODE = 1001;
private TextView textView;
//handler创建方法一 使用Callback
private Handler handler1=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
//handler创建方法二 重写handlerMessage()方法
private Handler handler2=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
textView.setText(msg.obj.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tv);
test();
}
//子线程创建发送消息给主线程,更新组件的显示,并且赋值消息
private void test() {
new Thread(()->{
//常规写法
Message message = new Message();
message.obj="Next";
message.what= MSG_CODE;
handler2.sendMessage(message);
}).start();
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello World!" />
RelativeLayout>
内存泄漏分析:
添加测试代码
package com.example.handler_sample;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
public static final int MSG_CODE = 1001;
private TextView textView;
//handler创建方法一 使用Callback
private Handler handler1=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
return false;
}
});
//handler创建方法二 重写handlerMessage()方法
private Handler handler2=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
textView.setText(msg.obj.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tv);
test();
}
//子线程创建发送消息给主线程,更新组件的显示,并且赋值消息
private void test() {
new Thread(()->{
//常规写法
Message message = new Message();
/* message.obj="Next";
message.what= MSG_CODE;
handler2.sendMessage(message);*/
try {
message.what=MSG_CODE;
TimeUnit.SECONDS.sleep(3);//休眠3秒 并且销毁Activity
handler1.sendMessage(message);//跳转到第二个界面
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("TAG", "onDestroy: ");
}
}
第二个Activity
package com.example.handler_sample;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_personal);
}
}
activity_personal.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hi,186"/>
</RelativeLayout>
让线程休眠三秒,发消息给handler1,然后跳转到第二个界面启动APP的时候,退出销毁掉Activity,但是三秒后还是会跳转到第二个界面,这里就出现了假销毁现象,也就是内存泄漏
在Activity销毁时,移除message
@Override
protected void onDestroy() {
super.onDestroy();
handler1.removeMessages(MSG_CODE);
Log.e("TAG", "onDestroy: ");
}
也是无济于事,因为线程休眠的时候还未将消息放入到消息队列中,销毁时remove的是空
使用sendMessageDelayed()延时发送
private void test() {
new Thread(()->{
//常规写法
Message message = new Message();
/* message.obj="Next";
message.what= MSG_CODE;
handler2.sendMessage(message);*/
message.what=MSG_CODE;
handler1.sendMessageDelayed(message,3000);
}).start();
}
此时销毁Activity即可终止发送
针对第一种方式的解决方法
private void test() {
new Thread(()->{
//常规写法
Message message = new Message();
message.what=MSG_CODE;
try {
TimeUnit.SECONDS.sleep(3);//休眠3秒 并且销毁Activity
if (handler1!=null)handler1.sendMessage(message);//跳转到第二个界面
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
handler1=null;
Log.e("TAG", "onDestroy: ");
}
当activity销毁时直接给hander1赋为null,线程判断hander1不为空再发送消息,这时候休眠结束就不会发送消息了
不推荐的写法
private Message message;
@Override
protected void onDestroy() {
super.onDestroy();
// handler1.removeMessages(MSG_CODE);
// handler1=null;
message.recycle();
Log.e("TAG", "onDestroy: ");
}
让Message设为全局变量,然后销毁的时候回收message,如果是发送延时消息没问题,如果是直接发送,则会抛出消息正在使用的异常
private void test() {
new Thread(()->{
new Handler();
}).start();
}
直接闪退
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
会抛出异常,因为应用启动时会调用ActivtyThead方法 其中的main方法
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
// Call per-process mainline module initialization.
initializeMainlineModules();
Process.setArgV0("" );
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
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的检测
Looper.prepareMainLooper();
进去prepareMainLooper()发现
@Deprecated
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
再进prepare(false)
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));
}
sThreadLocal.set(new Looper(quitAllowed));
它这里new了一个Looper 这个Looper是主线程的Looper,这个sThreadLocal是存在在ThreadLocalMap里的,查看其sThreadLocal.get()方法
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();
}
Thread t = Thread.currentThread();
从主线程的ThreadLocal去给get得到的是主线程做为key
ThreadLocalMap map = getMap(t);
然后从getmap去找,找不到就抛出异常
再看new Handler
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
mLooper = Looper.myLooper();
Looper从myLooper中去取
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
结果取的looper 是sThreadLocal.get()去主线程拿 从子线程拿是找不到的 因为上边发现sThreadLocal.get()是从ThreadLocalMap去取值。key已经设置为主线程,value是looper,此时的new Handler()是从子线程进去的所以拿不到,最终抛出异常
handler中
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mLooper为null就抛出异常,此时已经很清晰了
private void test() {
new Thread(()->{
textView.setText("andy");
}).start();
}
发现子线程是可以修改控件的
使用Toast
private void test() {
new Thread(()->{
Toast.makeText(this, "andy", Toast.LENGTH_SHORT).show();
}).start();
}
发现闪退,并且抛出异常
java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
设置线程休眠后修改TextView
private void test() {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
textView.setText("andy");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
发现休眠后闪退,抛出异常
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
进入setText方法找到
if (mLayout != null) {
checkForRelayout();
}
进入 checkForRelayout()方法
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.
int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
*/
makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {
autoSizeText();
invalidate();
return;
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return;
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}
发现其if else都会执行
requestLayout();
invalidate();
一个请求布局,一个刷新
同时TextView继承View
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
查看requestLayout()方法
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
ViewRootImpl是ViewPrivate的实现类
在ViewRootImpl中requestLayout()执行了checkThread()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
查看checkThread()方法
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
当前的Thread和存入的Thread不一致就会抛出异常
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
Toast result = new Toast(context, looper);
result.mText = text;
result.mDuration = duration;
return result;
} else {
Toast result = new Toast(context, looper);
View v = ToastPresenter.getTextToastView(context, text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
}
Toast()最后也调用了view
如果执行的时候invalidate()快于requestLayout()方法,那么就不会抛出异常,如果requestLayout()先执行一段再invalidate()就会抛出异常
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)
这里的异常说明也很清晰了ViewRootImpl.requestLayout ViewRootImpl.checkThread抛出异常主线程和子线程不一致
//handler创建方法一 使用Callback
private Handler handler1=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
return false;
}
});
//handler创建方法二 重写handlerMessage()方法
private Handler handler2=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
textView.setText(msg.obj.toString());
}
};
方法二是谷歌备胎api不推荐使用
Handler收消息是
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果mCallback不等于空,则直接返回,否则handleMessage(msg) 两个handleMessage(msg)是有差别的,一个是可以重写,一个是接口固定的方法
public void handleMessage(@NonNull Message msg) {
}
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
其上边执行的handleCallback就是开启run方法
private static void handleCallback(Message message) {
message.callback.run();
}
也就是子线程的run切换到主线程中,去执行run
创建测试方法
void test(){
final ThreadLocal<String> threadLocal=new ThreadLocal<String>(){
@Nullable
@Override
protected String initialValue() {
//重写初始化方法,默认返回为null,如果ThreadLocalMap拿不到值再调用初始化方法
threadLocal.get();
return "andy";
}
};
}
注意注释,查看threadLocal.get()方法
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;
}
key是主线程 value是T 是你定义的泛型
发现从threadLocal.get()获取的是主线程
注意:
在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。