我们都用过Handler,也知道handler有两种使用方式,
目录
* 1、Handler内存泄露测试
* 2、为什么不能在子线程创建Handler
* 3、textView.setText()只能在主线程执行,这句话是错误!
* 4、new Handler()两种写法有什么区别?
* 5、ThreadLocal用法和原理
1:实现方式
1:第一种
private Handler handler1 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
startActivity(new Intent(MainActivity.this, PersonalActivity.class));
return false;
}
});
第二种
// 这是谷歌备胎的api,不推荐使用
private Handler handler2 = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
textView.setText(msg.obj.toString());
}
};
2:内存泄露问题
摧毁activity
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
// 1、Handler内存泄露测试(假象)
SystemClock.sleep(1000); // 销毁Activity 线程休眠
Message message = new Message();
message.what=3;
handler1.sendMessage(message);
//
}
}).start();
}
虽然打印了activity 的ondestory()方法,还是继续了跳转的动作。
所以我们需要在onDestory中
@Override
protected void onDestroy() {
super.onDestroy();
Log.i("NetEase ", "onDestroy");
handler1.removeMessages(3);
handler1 = null;
}
如果handler不置为空,还是会跳转,如果置空以后就可以了。
3:为什么不能在子线程中创建handler
如果我们这样写
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
new Handler();
}
}).start();
}
01-02 07:31:06.201 27860-27882/com.netease.handler.sample E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.netease.handler.sample, PID: 27860
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.netease.handler.sample.MainActivity$3.run(MainActivity.java:60)
at java.lang.Thread.run(Thread.java:919)
为什么会报这个错呢?因为app启动时通过AcitivityThread的main()为程序入口,
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// 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();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
//重点看这个方法
Looper.prepareMainLooper();
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
//追踪这个方法
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
继续追踪
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));
}
在这个方法中,他创建了 Looper,这个Looper是主线程的Looper,这个sThreadLocal是存在ThreadLocalMap中,
比如set方法
public void set(T value) {
Thread t = Thread.currentThread();
//这个map集合,new完的Looper放到了Map中,key为当前的Thread
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
然后我们再看new Handler方法,
*/
public Handler() {
this(null, false);
}
public Handler(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());
}
}
//在创建Handler中会去取一个myLooper()
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;
}
他是直接去map中去取
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
因为程序启动的时候是通过主线程为key,放入了一个looper,现在用子线程为key去取looper,勘定取不到,所以才会报异常。
4:TextView.setTextView只能在主线程执行吗?我们试一下
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
textView.setText("我在测试~~~~");
}
}).start();
}
为什么呢?之前一直强调只能在主线程中执行。
换成Toast
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
//textView.setText("我在测试~~~~");
Toast.makeText(MainActivity.this,"我在测试",Toast.LENGTH_SHORT).show();
}
}).start();
}
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.netease.handler.sample, PID: 28357
java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
at android.widget.Toast$TN.<init>(Toast.java:407)
at android.widget.Toast.<init>(Toast.java:121)
at android.widget.Toast.makeText(Toast.java:286)
at android.widget.Toast.makeText(Toast.java:276)
at com.netease.handler.sample.MainActivity$3.run(MainActivity.java:60)
at java.lang.Thread.run(Thread.java:919)
再换一下:
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(1000);
textView.setText("我在测试~~~~");
// Toast.makeText(MainActivity.this,"我在测试",Toast.LENGTH_SHORT).show();
}
}).start();
}
闪退:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
为什么呢??
我们进入TextView中:
找到这样的方法
if (mLayout != null) {
checkForRelayout();
}
/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
*/
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)) {
//省略...
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();
}
}
都执行了这两个方法
requestLayout();
invalidate();
requestLayout是View中的方法
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* <p>Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.</p>
*/
@CallSuper
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:
public final class ViewRootImpl implements ViewParent,
mParent
protected ViewParent mParent;ViewParent是个接口
mParent.requestLayout();
追踪:
ViewRootImpl #requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这个异常就是TextView.setTextView中出现的异常,但是如果 invalidate(); 在 checkThread(); 方法执行前就执行完,那么就不会报异常。所以,TextView.setTextView只能在主线程执行这句话是错的 ,因为太过于绝对。
5:new Handler两种写法有什么区别?
上面有两种实现方式
我们先看如何去取消息:
ActivityThread#main()方法
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// 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();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
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.loop();
public static void loop() {
final Looper me = myLooper();
//省略
//关注这个循环
for (;;) {
//取消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
//通过handler去分发消息
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
//省略
msg.recycleUnchecked();
}
}
msg.target就是handler
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
//先关注这个方法 先判断mCallBack,如果不等于null那么就继续执行
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
其实没啥区别,当(mCallback.handleMessage(msg))为false的时候,就会去执行下面的handlerMessage方法,下面的就是一个可以重写的方法。所以把第二种方法叫备胎方法。
6:ThreadLocal用法和原理
正常的
Looper#中的ThreadLocal是Looper类型,且是static类型的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
测试下面常用的源码中的方法
ThreadLocal# set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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();
}
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;
}
Demo
public class ThreadLocalTest {
@Test
public void test() {
// 创建本地线程(主线程)---简单测试
final ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
// 重写初始化方法,默认返回null,如果ThreadLocalMap拿不到值再调用初始化方法
return "冯老师";
}
};
// 从ThreadLocalMap中获取String值,key是主线程
System.out.println("主线程threadLocal:" + threadLocal.get());
//--------------------------thread-0
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 从ThreadLocalMap中获取key:thread-0的值?没有,拿不到值再调用初始化方法
String value1 = threadLocal.get();
System.out.println("thread-0:" + threadLocal.get()); // 冯老师
// ThreadLocalMap存入:key:thread-0 value:"熊老师"
threadLocal.set("熊老师");
String value2 = threadLocal.get(); // 可以get到了
System.out.println("thread-0 set >>> " + threadLocal.get()); // 熊老师
// 使用完成建议remove(),避免大量无意义的内存占用
threadLocal.remove();
}
});
thread.start();
//--------------------------thread-1
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// 从ThreadLocalMap中获取key:thread-1的值?没有,拿不到值再调用初始化方法
String value1 = threadLocal.get();
System.out.println("thread-1:" + threadLocal.get()); // 冯老师
// ThreadLocalMap存入:key:thread-1 value:"彭老师"
threadLocal.set("彭老师");
String value2 = threadLocal.get(); // 可以get到了
System.out.println("thread-1 set >>> " + threadLocal.get()); // 彭老师
// 使用完成建议remove(),避免大量无意义的内存占用
threadLocal.remove();
}
});
thread2.start();
}
}
原理就是:注意Threadlocal最后的存取是以键值对的形式存在的,键就是当前的线程。